From 517881528495d0f74e24a09df90740246feb58d9 Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Sun, 17 Oct 2021 02:32:50 -0700 Subject: [PATCH 01/68] removing artifact --- _config.yml | 1 - 1 file changed, 1 deletion(-) delete mode 100755 _config.yml diff --git a/_config.yml b/_config.yml deleted file mode 100755 index 277f1f2c5..000000000 --- a/_config.yml +++ /dev/null @@ -1 +0,0 @@ -theme: jekyll-theme-cayman From cc3dcf40e0a5c470b265707471b9663817cc04be Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Tue, 2 Nov 2021 11:08:41 -0700 Subject: [PATCH 02/68] Sphinx docs (#76) * changing markdown format to rst * Commit basic files for sphinx. README changed to rst format * Fix latex rendering and theme, starting github action sphinx build * Docs build works as part of github workflows. Still needs to find a way to automate generation for S3 bucket (when that works, trigger should be pull request to main, after all checks have completed, or better: merge) * Refactor sphinx build, isolating script to help build locally too * copyright mods * Updated installation instructions with recent findings --- .../github_actions_automated_testing.yml | 7 +- README.md => README.rst | 93 +++++++++++-------- dev_tools/build_sphinx_docs.sh | 8 ++ docs/Makefile | 20 ++++ docs/make.bat | 35 +++++++ docs/source/README.rst | 1 + docs/source/_static/.keep | 0 docs/source/_templates/.keep | 0 docs/source/backendbuddy_basics.ipynb | 1 + docs/source/conf.py | 56 +++++++++++ docs/source/dmet.ipynb | 1 + docs/source/img | 1 + docs/source/index.rst | 22 +++++ docs/source/tutorials.rst | 8 ++ setup.py | 7 +- 15 files changed, 216 insertions(+), 44 deletions(-) rename README.md => README.rst (56%) mode change 100755 => 100644 create mode 100644 dev_tools/build_sphinx_docs.sh create mode 100644 docs/Makefile create mode 100644 docs/make.bat create mode 120000 docs/source/README.rst create mode 100644 docs/source/_static/.keep create mode 100644 docs/source/_templates/.keep create mode 120000 docs/source/backendbuddy_basics.ipynb create mode 100644 docs/source/conf.py create mode 120000 docs/source/dmet.ipynb create mode 120000 docs/source/img create mode 100644 docs/source/index.rst create mode 100644 docs/source/tutorials.rst diff --git a/.github/workflows/github_actions_automated_testing.yml b/.github/workflows/github_actions_automated_testing.yml index e80793f3b..35b58b7c9 100755 --- a/.github/workflows/github_actions_automated_testing.yml +++ b/.github/workflows/github_actions_automated_testing.yml @@ -17,9 +17,10 @@ jobs: with: python-version: ${{ matrix.python-version }} - - name: Install pip, pytest, jupyter + - name: Install pip, wheel, pytest, jupyter run: | python -m pip install --upgrade pip + pip install wheel pip install pytest pip install pytest-cov pip install jupyter @@ -45,7 +46,7 @@ jobs: - name: qsdk install run: | - python setup.py install + python -m pip install . if: always() - name: qsdk tests @@ -59,3 +60,5 @@ jobs: cd examples pytest --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html test_notebooks.py if: always() + + diff --git a/README.md b/README.rst old mode 100755 new mode 100644 similarity index 56% rename from README.md rename to README.rst index 1d0288e91..2b66e5f67 --- a/README.md +++ b/README.rst @@ -1,4 +1,5 @@ -# QEMIST_qSDK +qSDK overview +============= Welcome ! This open-source quantum SDK provides tools developed for exploring quantum chemistry simulation end-to-end workflows on @@ -10,80 +11,94 @@ It provides users with features such as resource estimation, access to various c quantum devices) and some classical solvers in order to keep track of both accuracy and resource requirements of our workflows, and facilitate the design of successful hardware experiments. -## Install +Install +------- This package requires a Python 3 environment. We recommend: -- using [Python virtual environments](https://docs.python.org/3/tutorial/venv.html) in order to set up your environment safely and cleanly -- installing the "dev" version of Python3 if you encounter missing header errors such as `python.h file not found`. -- having good C/C++ compilers and BLAS library to ensure the quantum circuit simulators you choose to install have good performance. -### Using pip + +* using `Python virtual environments `_ in order to set up your environment safely and cleanly +* installing the "dev" version of Python3 if you encounter missing header errors, such as ``python.h file not found``. +* having good C/C++ compilers and BLAS library to ensure the quantum circuit simulators you choose to install have good performance. + +Using pip +^^^^^^^^^ TODO: once this package is available on pypi, give the command. -### From source, using setuptools +From source, using setuptools +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -This package can be installed by first cloning this repository with `git clone`, and typing the following command in the +This package can be installed by first cloning this repository with ``git clone``\ , and typing the following command in the root directory: -``` -python setup.py install -``` + +.. code-block:: + + python -m pip install . If the installation of a dependency fails and the reason is not obvious, we suggest installing that dependency -separately with `pip`, before trying again. +separately with ``pip``\ , before trying again. -If you would like to modify or develop code in `qSDK`, you can add the path to this folder to your `PYTHONPATH` +If you would like to modify or develop code in ``qSDK``\ , you can add the path to this folder to your ``PYTHONPATH`` environment variable instead of installing it with pip: -``` -export PYTHONPATH=:$PYTHONPATH -``` -### Optional dependencies +.. code-block:: + + export PYTHONPATH=:$PYTHONPATH + +Optional dependencies +^^^^^^^^^^^^^^^^^^^^^ qSDK enables users to target various backends. In particular, it integrates quantum circuit simulators such as -`qulacs`, `qiskit`, `cirq` or `qdk`. We leave it to you to install the packages of your choice. -Backends such as `qulacs` and `cirq` show good overall performance. Most packages can be installed through pip in a straightforward way: -``` -pip install qulacs -pip install qiskit -pip install cirq -... -``` +``qulacs``\ , ``qiskit``\ , ``cirq`` or ``qdk``. We leave it to you to install the packages of your choice. +Backends such as ``qulacs`` and ``cirq`` show good overall performance. Most packages can be installed through pip in a straightforward way: + +.. code-block:: + + pip install qulacs + pip install qiskit + pip install cirq + ... Depending on your OS and environment, some of these packages may be more challenging to install. For installing Microsoft's QDK or any issue regarding the above packages, please check their respective documentation. -### Optional: environment variables +Optional: environment variables +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Some environment variables can impact performance (ex: using GPU for quantum circuit simulation, or changing the number of CPU threads used) or are used to connect to web services providing access to some compute backends. -See the list of relevant environment variables and their use in `env_var.sh`. In order for these variables to be set to +See the list of relevant environment variables and their use in ``env_var.sh``. In order for these variables to be set to the desired values in your environment, you can run this shell script in Linux with the following command line: -`source env_var.sh` (you may need to set execution permissions with `chmod +x set_env_var.sh` first), or you can set -them in whatever way your OS supports it, or even inside your python script using the `os` package. +``source env_var.sh`` (you may need to set execution permissions with ``chmod +x set_env_var.sh`` first), or you can set +them in whatever way your OS supports it, or even inside your python script using the ``os`` package. -## Docs +Docs +---- TODO: insert sentence and link to sphinx documentation when its online. -## Tutorials +Tutorials +--------- -Please check the `examples` folder jupyter notebook tutorials and other examples. +Please check the ``examples`` folder jupyter notebook tutorials and other examples. TODO: recommend here the one we are doing about the DMET paper once its ready and merged -## Tests +Tests +----- + +Unit tests can be found in the ``tests`` folders, located in the various toolboxes they are related to. To automatically +find and run all tests (assuming you are in the ``qsdk`` subfolder that contains the code of the package): -Unit tests can be found in the `tests` folders, located in the various toolboxes they are related to. To automatically -find and run all tests (assuming you are in the `qsdk` subfolder that contains the code of the package): +.. code-block:: -``` -python -m unittest -``` + python -m unittest -## Citations +Citations +--------- If you use qSDK in your research, please cite diff --git a/dev_tools/build_sphinx_docs.sh b/dev_tools/build_sphinx_docs.sh new file mode 100644 index 000000000..145a0a486 --- /dev/null +++ b/dev_tools/build_sphinx_docs.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +# Make sure you build qsdk and all relevant dependencies before attempting to generate documentations +# or mock the desired packages in docs/source/conf.py +cd ../docs || cd docs +pip install sphinx sphinx_rtd_theme nbsphinx +sudo apt-get install -y latexmk texlive-latex-recommended texlive-latex-extra texlive-fonts-recommended pandoc dvipng +make clean; make html diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 000000000..d0c3cbf10 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 000000000..6fcf05b4b --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/source/README.rst b/docs/source/README.rst new file mode 120000 index 000000000..c768ff7d9 --- /dev/null +++ b/docs/source/README.rst @@ -0,0 +1 @@ +../../README.rst \ No newline at end of file diff --git a/docs/source/_static/.keep b/docs/source/_static/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/source/_templates/.keep b/docs/source/_templates/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/docs/source/backendbuddy_basics.ipynb b/docs/source/backendbuddy_basics.ipynb new file mode 120000 index 000000000..7ee227138 --- /dev/null +++ b/docs/source/backendbuddy_basics.ipynb @@ -0,0 +1 @@ +../../examples/backendbuddy/1.the_basics.ipynb \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 000000000..796bb059e --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,56 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = 'qsdk' +copyright = '2021 1QBit' +author = 'Valentin senicourt, Alexandre Fleury, Ryan Day, James Brown' + +# The full version, including alpha/beta/rc tags +release = '0.2.0' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.napoleon', 'nbsphinx', 'sphinx.ext.imgmath'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '**.ipynb_checkpoints', 'qdk_template.*', '**test**'] +#source_suffix = {'.rst': 'restructuredtext', '.md': 'markdown'} + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +autodoc_mock_imports = ['qemist-client', 'PIL', 'Pillow', 'qsharp'] diff --git a/docs/source/dmet.ipynb b/docs/source/dmet.ipynb new file mode 120000 index 000000000..0068b0625 --- /dev/null +++ b/docs/source/dmet.ipynb @@ -0,0 +1 @@ +../../examples/dmet.ipynb \ No newline at end of file diff --git a/docs/source/img b/docs/source/img new file mode 120000 index 000000000..73338c245 --- /dev/null +++ b/docs/source/img @@ -0,0 +1 @@ +../../examples/img \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 000000000..bdc32c2a7 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,22 @@ +.. qsdk documentation master file, created by + sphinx-quickstart on Fri Oct 15 15:20:40 2021. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to qsdk's documentation! +================================ + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + README + qsdk + tutorials + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/source/tutorials.rst b/docs/source/tutorials.rst new file mode 100644 index 000000000..64333e5bd --- /dev/null +++ b/docs/source/tutorials.rst @@ -0,0 +1,8 @@ +Tutorials +========= + +.. toctree:: + :maxdepth: 2 + + backendbuddy_basics + dmet diff --git a/setup.py b/setup.py index 249550bed..9858df848 100755 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ def install(package): subprocess.check_call([sys.executable, "-m", "pip", "install", package]) -with open('README.md', 'r') as f: +with open('README.rst', 'r') as f: long_description = f.read() __title__ = "1QBit's quantum SDK for quantum chemistry" @@ -16,15 +16,16 @@ def install(package): __status__ = "beta" __authors__ = ["Valentin Senicourt, Alexandre Fleury, Ryan Day, James Brown"] +install('wheel') install('h5py==3.2.0') -install('pyscf') +install('pyscf==1.7.6.post1') setuptools.setup( name="qSDK", version=__version__, description="1QBit's quantum SDK for quantum chemistry on quantum computers and simulators", long_description=long_description, - long_description_content_type="text/markdown", + long_description_content_type="text/x-rst", url="https://github.com/quantumsimulation/QEMIST_qSDK", packages=setuptools.find_packages(), test_suite="qsdk", From d71c468fd8d45d33bcab9d171eef5300c03e94e2 Mon Sep 17 00:00:00 2001 From: AlexandreF-1qbit <76115575+AlexandreF-1qbit@users.noreply.github.com> Date: Tue, 2 Nov 2021 15:37:09 -0400 Subject: [PATCH 03/68] Fixing CCSDSolver.get_rdm() with frozen orbitals and energy from RDMs (#81) * Fix frozen orbitals inconsistency in CCSDSolver with other get_rdm methods. * Added method to compute energy from RDMs. --- qsdk/algorithms/classical/ccsd_solver.py | 16 +++++-- .../molecular_computation/molecule.py | 48 ++++++++++++++++++- .../tests/test_molecule.py | 21 ++++++++ 3 files changed, 81 insertions(+), 4 deletions(-) diff --git a/qsdk/algorithms/classical/ccsd_solver.py b/qsdk/algorithms/classical/ccsd_solver.py index 4870207fc..1e041839e 100644 --- a/qsdk/algorithms/classical/ccsd_solver.py +++ b/qsdk/algorithms/classical/ccsd_solver.py @@ -15,7 +15,8 @@ """Class performing electronic structure calculation employing the CCSD method. """ -from pyscf import cc +from pyscf import cc, lib +from pyscf.cc.ccsd_rdm import _make_rdm1, _make_rdm2, _gamma1_intermediates, _gamma2_outcore from qsdk.algorithms.electronic_structure_solver import ElectronicStructureSolver @@ -73,7 +74,16 @@ def get_rdm(self): # Solve the lambda equation and obtain the reduced density matrix from CC calculation self.cc_fragment.solve_lambda() - one_rdm = self.cc_fragment.make_rdm1() - two_rdm = self.cc_fragment.make_rdm2() + t1 = self.cc_fragment.t1 + t2 = self.cc_fragment.t2 + l1 = self.cc_fragment.l1 + l2 = self.cc_fragment.l2 + + d1 = _gamma1_intermediates(self.cc_fragment, t1, t2, l1, l2) + f = lib.H5TmpFile() + d2 = _gamma2_outcore(self.cc_fragment, t1, t2, l1, l2, f, False) + + one_rdm = _make_rdm1(self.cc_fragment, d1, with_frozen=False) + two_rdm = _make_rdm2(self.cc_fragment, d1, d2, with_dm1=True, with_frozen=False) return one_rdm, two_rdm diff --git a/qsdk/toolboxes/molecular_computation/molecule.py b/qsdk/toolboxes/molecular_computation/molecule.py index ea3ad6d3d..46d36cc77 100644 --- a/qsdk/toolboxes/molecular_computation/molecule.py +++ b/qsdk/toolboxes/molecular_computation/molecule.py @@ -19,9 +19,10 @@ import copy from dataclasses import dataclass, field import numpy as np -from pyscf import gto, scf +from pyscf import gto, scf, ao2mo import openfermion import openfermionpyscf +from openfermion.ops.representations.interaction_operator import get_active_space_integrals from qsdk.toolboxes.molecular_computation.frozen_orbitals import get_frozen_core from qsdk.toolboxes.qubit_mappings.mapping_transform import get_fermion_operator @@ -364,3 +365,48 @@ def freeze_mos(self, frozen_orbitals, inplace=True): copy_self.frozen_virtual = list_of_active_frozen[3] return copy_self + + def energy_from_rdms(self, one_rdm, two_rdm): + """Computes the molecular energy from one- and two-particle reduced + density matrices (RDMs). Coefficients (integrals) are computed + on-the-fly using a pyscf object and the mean-field. Frozen orbitals + are supported with this method. + + Args: + one_rdm (numpy.array): One-particle density matrix in MO basis. + two_rdm (numpy.array): Two-particle density matrix in MO basis. + + Returns: + float: Molecular energy. + """ + + # Pyscf molecule to get integrals. + pyscf_mol = self.to_pyscf(self.basis) + + # Corresponding to nuclear repulsion energy and static coulomb energy. + core_constant = float(pyscf_mol.energy_nuc()) + + # get_hcore is equivalent to int1e_kin + int1e_nuc. + one_electron_integrals = self.mean_field.mo_coeff.T @ self.mean_field.get_hcore() @ self.mean_field.mo_coeff + + # Getting 2-body integrals in atomic and converting to molecular basis. + two_electron_integrals = ao2mo.kernel(pyscf_mol.intor("int2e"), self.mean_field.mo_coeff) + two_electron_integrals = ao2mo.restore(1, two_electron_integrals, len(self.mean_field.mo_coeff)) + + # PQRS convention in openfermion: + # h[p,q]=\int \phi_p(x)* (T + V_{ext}) \phi_q(x) dx + # h[p,q,r,s]=\int \phi_p(x)* \phi_q(y)* V_{elec-elec} \phi_r(y) \phi_s(x) dxdy + # The convention is not the same with PySCF integrals. So, a change is + # made and reverse back after performing the truncation for frozen + # orbitals. + two_electron_integrals = two_electron_integrals.transpose(0, 2, 3, 1) + core_offset, one_electron_integrals, two_electron_integrals = get_active_space_integrals(one_electron_integrals, two_electron_integrals, self.frozen_occupied, self.active_mos) + two_electron_integrals = two_electron_integrals.transpose(0, 3, 1, 2) + + # Adding frozen electron contribution to core constant. + core_constant += core_offset + + # Computing the total energy from integrals and provided RDMs. + e = core_constant + np.sum(one_electron_integrals * one_rdm) + 0.5*np.sum(two_electron_integrals * two_rdm) + + return e.real diff --git a/qsdk/toolboxes/molecular_computation/tests/test_molecule.py b/qsdk/toolboxes/molecular_computation/tests/test_molecule.py index 48ca87d0e..aca1fef39 100644 --- a/qsdk/toolboxes/molecular_computation/tests/test_molecule.py +++ b/qsdk/toolboxes/molecular_computation/tests/test_molecule.py @@ -15,6 +15,7 @@ import unittest from qsdk import SecondQuantizedMolecule +from qsdk.molecule_library import mol_H2_sto3g from qsdk.toolboxes.molecular_computation.molecule import atom_string_to_list @@ -123,6 +124,26 @@ def test_get_fermionic_hamiltonian(self): shift = molecule.fermionic_hamiltonian.constant self.assertAlmostEqual(shift, -51.47120372466002, delta=1e-6) + def test_energy_from_rdms(self): + """Verify energy computation from RDMs.""" + + rdm1 = [[ 1.97453997e+00, -7.05987336e-17], + [-7.05987336e-17, 2.54600303e-02]] + + rdm2 = [ + [[[ 1.97453997e+00, -7.96423130e-17], + [-7.96423130e-17, 3.21234218e-33]], + [[-7.96423130e-17, -2.24213843e-01], + [ 0.00000000e+00, 9.04357944e-18]]], + [[[-7.96423130e-17, 0.00000000e+00], + [-2.24213843e-01, 9.04357944e-18]], + [[ 3.21234218e-33, 9.04357944e-18], + [ 9.04357944e-18, 2.54600303e-02]]] + ] + + e_rdms = mol_H2_sto3g.energy_from_rdms(rdm1, rdm2) + self.assertAlmostEqual(e_rdms, -1.1372701, delta=1e-5) + if __name__ == "__main__": unittest.main() From 11605faddeb2a19b0c51c4db37faac2fdeb3a7b4 Mon Sep 17 00:00:00 2001 From: AlexandreF-1qbit <76115575+AlexandreF-1qbit@users.noreply.github.com> Date: Tue, 2 Nov 2021 15:39:48 -0400 Subject: [PATCH 04/68] Removing variable names in Returns - docstrings (#79) * Deleted variable names in Returns docstrings. Also some minor typos (spaces, formatting, ...). * Refactor docstrings and tuple returns. --- qsdk/algorithms/classical/ccsd_solver.py | 15 +- qsdk/algorithms/classical/fci_solver.py | 6 +- .../classical/semi_empirical_solver.py | 2 +- .../algorithms/electronic_structure_solver.py | 4 +- qsdk/algorithms/variational/vqe_solver.py | 10 +- qsdk/backendbuddy/circuit.py | 74 +++--- qsdk/backendbuddy/gate.py | 29 ++- .../helpers/circuits/measurement_basis.py | 21 +- .../helpers/operators/operators.py | 25 +- .../noisy_simulation/noise_models.py | 41 ++-- qsdk/backendbuddy/qdk_template.py | 7 +- .../qpu_connection/honeywell_connection.py | 68 +++--- .../qpu_connection/ionq_connection.py | 80 ++++--- .../qpu_connection/qemist_cloud_connection.py | 77 +++--- .../qpu_connection/qpu_connection.py | 18 +- qsdk/backendbuddy/simulator.py | 220 ++++++++++-------- .../translator/translate_braket.py | 30 +-- .../backendbuddy/translator/translate_cirq.py | 33 +-- .../translator/translate_json_ionq.py | 34 +-- .../translator/translate_openqasm.py | 65 +++--- .../translator/translate_projectq.py | 55 +++-- qsdk/backendbuddy/translator/translate_qdk.py | 35 +-- .../translator/translate_qiskit.py | 30 +-- .../translator/translate_qulacs.py | 37 +-- qsdk/helpers/utils.py | 6 +- .../dmet/_helpers/dmet_bath.py | 14 +- .../dmet/_helpers/dmet_fragment.py | 8 +- .../dmet/_helpers/dmet_onerdm.py | 10 +- .../dmet/_helpers/dmet_orbitals.py | 9 +- .../dmet/_helpers/dmet_scf.py | 10 +- .../dmet/_helpers/dmet_scf_guess.py | 2 +- .../dmet/dmet_problem_decomposition.py | 8 +- .../electron_localization/iao_localization.py | 19 +- .../oniom/_helpers/helper_classes.py | 8 +- .../ansatz_generator/fermionic_operators.py | 12 +- .../ansatz_generator/penalty_terms.py | 8 +- qsdk/toolboxes/ansatz_generator/rucc.py | 13 +- qsdk/toolboxes/ansatz_generator/uccsd.py | 6 +- qsdk/toolboxes/ansatz_generator/upccgsd.py | 4 +- .../ansatz_generator/variational_circuit.py | 2 +- .../measurements/estimate_measurements.py | 31 +-- .../measurements/qubit_terms_grouping.py | 69 +++--- .../molecular_computation/frozen_orbitals.py | 5 +- .../molecular_computation/molecule.py | 12 +- qsdk/toolboxes/operators/operators.py | 6 +- .../post_processing/bootstrapping.py | 3 +- .../mc_weeny_rdm_purification.py | 4 +- .../toolboxes/qubit_mappings/bravyi_kitaev.py | 3 +- .../toolboxes/qubit_mappings/jordan_wigner.py | 4 +- .../qubit_mappings/mapping_transform.py | 10 +- .../qubit_mappings/statevector_mapping.py | 14 +- .../symmetry_conserving_bravyi_kitaev.py | 12 +- 52 files changed, 740 insertions(+), 588 deletions(-) diff --git a/qsdk/algorithms/classical/ccsd_solver.py b/qsdk/algorithms/classical/ccsd_solver.py index 1e041839e..f80c286b8 100644 --- a/qsdk/algorithms/classical/ccsd_solver.py +++ b/qsdk/algorithms/classical/ccsd_solver.py @@ -43,8 +43,8 @@ def __init__(self, molecule): def simulate(self): """Perform the simulation (energy calculation) for the molecule. - Returns: - total_energy (float): CCSD energy + Returns: + float: CCSD energy. """ # Execute CCSD calculation self.cc_fragment = cc.CCSD(self.mean_field, frozen=self.frozen) @@ -61,11 +61,12 @@ def get_rdm(self): """Calculate the 1- and 2-particle reduced density matrices. The CCSD lambda equation will be solved for calculating the RDMs. - Returns: - one_rdm, two_rdm (numpy.array, numpy.array): One & two-particle - RDMs - Raises: - RuntimeError: If no simulation has been run. + Returns: + numpy.array: One-particle RDM. + numpy.array: Two-particle RDM. + + Raises: + RuntimeError: If no simulation has been run. """ # Check if CCSD calculation is performed diff --git a/qsdk/algorithms/classical/fci_solver.py b/qsdk/algorithms/classical/fci_solver.py index 6c080564b..e70803e53 100644 --- a/qsdk/algorithms/classical/fci_solver.py +++ b/qsdk/algorithms/classical/fci_solver.py @@ -72,7 +72,7 @@ def simulate(self): """Perform the simulation (energy calculation) for the molecule. Returns: - energy (float): Total FCI energy. + float: Total FCI energy. """ if self.cas: # Use previously generated effective Hamiltonian to obtain FCI solution @@ -103,8 +103,8 @@ def get_rdm(self): """Compute the Full CI 1- and 2-particle reduced density matrices. Returns: - one_rdm, two_rdm (numpy.array, numpy.array): One & two-particle - RDMs. + numpy.array: One-particle RDM. + numpy.array: Two-particle RDM. Raises: RuntimeError: If method "simulate" hasn't been run. diff --git a/qsdk/algorithms/classical/semi_empirical_solver.py b/qsdk/algorithms/classical/semi_empirical_solver.py index 1f2680f54..ce7c1986b 100644 --- a/qsdk/algorithms/classical/semi_empirical_solver.py +++ b/qsdk/algorithms/classical/semi_empirical_solver.py @@ -56,7 +56,7 @@ def simulate(self): """Perform the simulation (energy calculation) for the molecule. Returns: - total_energy (float): RMINDO3 energy. + float: RMINDO3 energy. """ solver = mindo3.RMINDO3(self.molecule.to_pyscf()).run(verbose=0) diff --git a/qsdk/algorithms/electronic_structure_solver.py b/qsdk/algorithms/electronic_structure_solver.py index 08081b7e1..63ee6c916 100644 --- a/qsdk/algorithms/electronic_structure_solver.py +++ b/qsdk/algorithms/electronic_structure_solver.py @@ -52,8 +52,8 @@ def get_rdm(self): the reduced density matrix. Returns: - (numpy.array, numpy.array): The one- and two-particle RDMs - (float64). + numpy.array: One-particle RDM. + numpy.array: Two-particle RDM. Raises: RuntimeError: If no simulation has been run. diff --git a/qsdk/algorithms/variational/vqe_solver.py b/qsdk/algorithms/variational/vqe_solver.py index e4d7733e7..870fb62b0 100644 --- a/qsdk/algorithms/variational/vqe_solver.py +++ b/qsdk/algorithms/variational/vqe_solver.py @@ -224,8 +224,9 @@ def energy_estimation(self, var_params): Args: var_params (numpy.array or str): variational parameters to use for VQE energy evaluation. + Returns: - energy (float): energy computed by VQE using the ansatz and input + float: energy computed by VQE using the ansatz and input variational parameters. """ @@ -262,8 +263,8 @@ def operator_expectation(self, operator, var_params=None, n_active_mos=None, n_a QubitHamiltonian. Returns: - expectation (float): operator expectation value computed by VQE - using the ansatz and input variational parameters. + float: operator expectation value computed by VQE using the + ansatz and input variational parameters. """ if var_params is None: var_params = self.ansatz.var_params @@ -445,7 +446,8 @@ def _default_optimizer(self, func, var_params): var_params (list): The variational parameters (float64). Returns: - The optimal energy found by the optimizer. + float: The optimal energy found by the optimizer. + list of floats: Optimal parameters """ from scipy.optimize import minimize diff --git a/qsdk/backendbuddy/circuit.py b/qsdk/backendbuddy/circuit.py index 5ae6529d0..d3efc88c2 100644 --- a/qsdk/backendbuddy/circuit.py +++ b/qsdk/backendbuddy/circuit.py @@ -12,10 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" - This abstract quantum circuit class allows to represent a quantum circuit, described by successive abstract - quantum gate operations, without tying it to a particular backend. It also provides methods to compute some of its - characteristics (width, size ...) +"""This abstract quantum circuit class allows to represent a quantum circuit, +described by successive abstract quantum gate operations, without tying it to a +particular backend. It also provides methods to compute some of its +characteristics (width, size ...). """ from typing import List @@ -23,22 +23,25 @@ class Circuit: - """ - An abstract quantum circuit class, represented by a list of abstract gate operations acting on qubits indices. - From this list of gates, the gate counts, width and other properties can be computed. - In the future, this information allows for finding unentangled registers or qubits only left in a classical - state. - - The attributes of the Circuit class should not be modified by the user: the add_gate method will ensure - their value is correct as gates are added to the circuit object. - - It is however ok to modify the value of the parameters for variational gate operations in a - sub-class of Circuit, as this does not impact the quantum circuit information. An example of how to do this to - engineer a parameterized ansatz to be used in a variational algorithm in available in the example folder. + """An abstract quantum circuit class, represented by a list of abstract gate + operations acting on qubits indices. From this list of gates, the gate + counts, width and other properties can be computed. In the future, this + information allows for finding unentangled registers or qubits only left in + a classical state. + + The attributes of the Circuit class should not be modified by the user: the + add_gate method will ensure their value is correct as gates are added to the + circuit object. + + It is however ok to modify the value of the parameters for variational gate + operations in a sub-class of Circuit, as this does not impact the quantum + circuit information. An example of how to do this to engineer a + parameterized ansatz to be used in a variational algorithm in available in + the example folder. """ def __init__(self, gates: List[Gate] = None, n_qubits=None): - """ Initialize gate list and internal variables depending on user input. """ + """Initialize gate list and internal variables depending on user input.""" self._gates = list() self._qubits_simulated = n_qubits @@ -50,7 +53,9 @@ def __init__(self, gates: List[Gate] = None, n_qubits=None): _ = [self.add_gate(g) for g in gates] def __str__(self): - """ Print info about the circuit and the gates it contains in a human-friendly format """ + """Print info about the circuit and the gates it contains in a + human-friendly format. + """ mystr = f"Circuit object. Size {self.size} \n\n" for abs_gate in self._gates: @@ -58,40 +63,47 @@ def __str__(self): return mystr def __add__(self, other): - """ Concatenate the list of instructions of two circuit objects into a single one. """ + """Concatenate the list of instructions of two circuit objects into a + single one. + """ return Circuit(self._gates + other._gates, n_qubits=max(self.width, other.width)) @property def size(self): - """ The size is the number of gates in the circuit. It is different from the depth. """ + """The size is the number of gates in the circuit. It is different from + the depth. + """ return len(self._gates) @property def width(self): - """ Return the number of qubits required by this circuit. Assume all qubits are needed, even those who do not - appear in a gate instruction. Qubit indexing starts at 0. """ + """Return the number of qubits required by this circuit. Assume all + qubits are needed, even those who do not appear in a gate instruction. + Qubit indexing starts at 0. + """ return max(self._qubit_indices) + 1 if self._qubit_indices else 0 @property def counts(self): - """ Return the counts for all gate types included in the circuit """ + """Return the counts for all gate types included in the circuit.""" return self._gate_counts @property def is_variational(self): - """ Returns true if the circuit holds any variational gate """ + """Returns true if the circuit holds any variational gate.""" return True if self._variational_gates else False @property def is_mixed_state(self): - """ Assume circuit leads to a mixed state due to mid-circuit measurement if any MEASURE gate was - explicitly added by the user """ + """Assume circuit leads to a mixed state due to mid-circuit measurement + if any MEASURE gate was explicitly added by the user. + """ return "MEASURE" in self.counts def add_gate(self, gate): - """ - Add a new gate to a circuit object and update other fields of the circuit object to gradually keep track - of its properties (gate count, qubit indices...) + """Add a new gate to a circuit object and update other fields of the + circuit object to gradually keep track of its properties (gate count, + qubit indices...). """ # Add the gate to the list of gates self._gates += [gate] @@ -101,7 +113,9 @@ def add_gate(self, gate): self._variational_gates.append(gate) def check_index_valid(index): - """ If circuit size was specified at instantiation, check that qubit indices do not go beyond it """ + """If circuit size was specified at instantiation, check that qubit + indices do not go beyond it. + """ if self._qubits_simulated: if index >= self._qubits_simulated: raise ValueError(f"Qubit index beyond expected maximal index ({self._qubits_simulated-1})\n " diff --git a/qsdk/backendbuddy/gate.py b/qsdk/backendbuddy/gate.py index ce11263bc..64a4ad638 100644 --- a/qsdk/backendbuddy/gate.py +++ b/qsdk/backendbuddy/gate.py @@ -12,9 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" - Define an abstract quantum gate class holding information about a quantum gate operation, without tying it - to a particular backend or an underlying mathematical operation. +"""Define an abstract quantum gate class holding information about a quantum +gate operation, without tying it to a particular backend or an underlying +mathematical operation. """ ONE_QUBIT_GATES = {"H", "X", "Y", "Z", "S", "T", "RX", "RY", "RZ"} @@ -22,16 +22,19 @@ class Gate(dict): - """ - An abstract gate class that exposes all the gate information such as gate name, control and target qubit indices, - parameter values. Assumes qubit indexation starts at index 0. + """An abstract gate class that exposes all the gate information such as gate + name, control and target qubit indices, parameter values. Assumes qubit + indexation starts at index 0. Attributes: - name (str): the gate name - target (int): A positive integer denoting the index of the target qubit - control (int): A positive integer denoting the index of the control qubit - parameter: A floating-point number or symbolic expression that will resolve at runtime. Can hold anything. - is_variational (bool): a boolean indicating if the gate operation is variational or not + name (str): the gate name, + target (int): A positive integer denoting the index of the target qubit, + control (int): A positive integer denoting the index of the control + qubit, + parameter: A floating-point number or symbolic expression that will + resolve at runtime. Can hold anything. + is_variational (bool): a boolean indicating if the gate operation is + variational or not. """ # TODO: extend control to a list to support gates such as the Toffoli gate etc in the future @@ -48,7 +51,9 @@ def __init__(self, name: str, target: int, control: int=None, parameter="", is_v "parameter": parameter, "is_variational": is_variational} def __str__(self): - """ Print gate information in a somewhat formatted way. Do not print empty attributes. """ + """Print gate information in a somewhat formatted way. Do not print + empty attributes. + """ mystr = f"{self.name:<10}" for attr in ["target", "control"]: diff --git a/qsdk/backendbuddy/helpers/circuits/measurement_basis.py b/qsdk/backendbuddy/helpers/circuits/measurement_basis.py index 76b67ed06..de4fe08b8 100644 --- a/qsdk/backendbuddy/helpers/circuits/measurement_basis.py +++ b/qsdk/backendbuddy/helpers/circuits/measurement_basis.py @@ -12,19 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" helper function: pauli word rotations """ +"""Helper function: pauli word rotations.""" import numpy as np from qsdk.backendbuddy import Gate def measurement_basis_gates(term): - """ Generate the rotation gates to perform change of basis before measurement + """Generate the rotation gates to perform change of basis before + measurement. - Args: - term: Openfermion-style term. Essentially a list of (int, str) tuples. - Returns: - A list containing the rotation gates. + Args: + term: Openfermion-style term. Essentially a list of (int, str) tuples. + + Returns: + list of Gate: A list containing the rotation gates. """ gates = [] for qubit_index, pauli in term: @@ -40,13 +42,16 @@ def measurement_basis_gates(term): def pauli_string_to_of(pauli_string): - """ Converts a string of I,X,Y,Z Pauli operators to an Openfermion-style representation """ + """ Converts a string of I,X,Y,Z Pauli operators to an Openfermion-style + representation. + """ return [(i, p) for i, p in enumerate(pauli_string) if p != 'I'] def pauli_of_to_string(pauli_op, n_qubits): """ Converts an Openfermion-style Pauli word to a string representation. - The user must specify the total number of qubits. """ + The user must specify the total number of qubits. + """ p_string = ['I'] * n_qubits for i, p in pauli_op: p_string[i] = p diff --git a/qsdk/backendbuddy/helpers/operators/operators.py b/qsdk/backendbuddy/helpers/operators/operators.py index 4a1a5ab76..0e3c276fb 100644 --- a/qsdk/backendbuddy/helpers/operators/operators.py +++ b/qsdk/backendbuddy/helpers/operators/operators.py @@ -12,8 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" Helper functions on QubitOperators, such as exporting Openfermion QubitOperator to file and vice-versa, or - to visualize its coefficients for example. """ +"""Helper functions on QubitOperators, such as exporting Openfermion +QubitOperator to file and vice-versa, or to visualize its coefficients for +example. +""" import math import numpy as np @@ -21,7 +23,9 @@ def ham_of_to_string(of_qb_ham): - """ Converts an Openfermion QubitOperator into a string with information for a Pauli word per line """ + """Converts an Openfermion QubitOperator into a string with information for + a Pauli word per line. + """ res = "" for k, v in of_qb_ham.terms.items(): res += f'{str(v)}\t{str(k)}\n' @@ -29,8 +33,10 @@ def ham_of_to_string(of_qb_ham): def string_ham_to_of(string_ham): - """ Reverse function of ham_of_to_string : reads a Hamiltonian from a file that uses the Openfermion syntax, - loads it into an openfermion QubitOperator """ + """Reverse function of ham_of_to_string : reads a Hamiltonian from a file + that uses the Openfermion syntax, loads it into an openfermion + QubitOperator. + """ of_terms_dict = dict() string_ham = string_ham.split('\n')[:-1] @@ -44,9 +50,12 @@ def string_ham_to_of(string_ham): def print_histogram_coeffs(qb_ham): - """ Convenience function printing a matplotlib histogram of the magnitudes of the coefficient in a - QubitOperator object. Combine with the compress method of the QubitOperator class, this allows users to - quickly identify what terms in the operator can be discarded, depending on the target accuracy of calculations. """ + """Convenience function printing a matplotlib histogram of the magnitudes + of the coefficient in a QubitOperator object. Combine with the compress + method of the QubitOperator class, this allows users to quickly identify + what terms in the operator can be discarded, depending on the target + accuracy of calculations. + """ import matplotlib matplotlib.use('TkAgg') diff --git a/qsdk/backendbuddy/noisy_simulation/noise_models.py b/qsdk/backendbuddy/noisy_simulation/noise_models.py index 009523159..441a698a9 100644 --- a/qsdk/backendbuddy/noisy_simulation/noise_models.py +++ b/qsdk/backendbuddy/noisy_simulation/noise_models.py @@ -12,12 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" - Generic noise model representation and backend-specific translation function. - The Simulator class is responsible for taking in the generic noise model at runtime and applying it accordingly - to the target compute backend. +"""Generic noise model representation and backend-specific translation function. +The Simulator class is responsible for taking in the generic noise model at +runtime and applying it accordingly to the target compute backend. - Only works for simulators supporting noisy simulation. +Only works for simulators supporting noisy simulation. """ @@ -34,15 +33,15 @@ class NoiseModel: - """ - A class representing a noise model. Not specific to any compute backend. - The object holds a dictionary mapping each noisy gate to a list of noise that should be applied every time it is - encountered in a circuit. - - Pauli noise expects a list of 3 probabilities corresponding to X,Y,Z pauli noises (I is deduced from the sum) - Depolarization noise is a special case of Pauli noise, where the 3 probabilities are equal: a float is enough. - Please check the notebook tutorial in the example section more a more in-depth description of the different - noise channels. + """A class representing a noise model. Not specific to any compute backend. + The object holds a dictionary mapping each noisy gate to a list of noise + that should be applied every time it is encountered in a circuit. + + Pauli noise expects a list of 3 probabilities corresponding to X,Y,Z pauli + noises (I is deduced from the sum) Depolarization noise is a special case of + Pauli noise, where the 3 probabilities are equal: a float is enough. Please + check the notebook tutorial in the example section more a more in-depth + description of the different noise channels. """ def __init__(self): @@ -52,7 +51,9 @@ def __repr__(self): return str(self._quantum_errors) def add_quantum_error(self, abs_gate, noise_type, noise_params): - """ Adds the desired noise to the gate specified by user. Checks if inputs makes sense.""" + """Adds the desired noise to the gate specified by user. Checks if + inputs makes sense. + """ if noise_type not in SUPPORTED_NOISE_MODELS: raise ValueError(f"Error model not supported :{noise_type}\nCurrently supported:{SUPPORTED_NOISE_MODELS}") @@ -75,8 +76,10 @@ def noisy_gates(self): def get_qiskit_noise_dict(noise_model): - """ Takes in generic noise model, converts to a dictionary whose keys are the qiskit basis gates supported - by the qasm simulator, to the noises, ensuring there are no redundancy / duplicates on the U-gates in particular.""" + """Takes in generic noise model, converts to a dictionary whose keys are + the qiskit basis gates supported by the qasm simulator, to the noises, + ensuring there are no redundancy / duplicates on the U-gates in particular. + """ qnd = dict() for gate, noises in noise_model._quantum_errors.items(): @@ -93,7 +96,9 @@ def get_qiskit_noise_dict(noise_model): def get_qiskit_noise_model(noise_model): - """ Takes a NoiseModel object as input, returns an equivalent Qiskit Noise Model, compatible with QASM simulator """ + """Takes a NoiseModel object as input, returns an equivalent Qiskit Noise + Model, compatible with QASM simulator. + """ from qiskit.providers.aer.noise import NoiseModel as QiskitNoiseModel from qiskit.providers.aer.noise.errors import depolarizing_error, pauli_error diff --git a/qsdk/backendbuddy/qdk_template.py b/qsdk/backendbuddy/qdk_template.py index 478b420f8..0b935e349 100644 --- a/qsdk/backendbuddy/qdk_template.py +++ b/qsdk/backendbuddy/qdk_template.py @@ -12,10 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" - This python module stores template Q# strings that can be used to have Q# code files ready - to be shared with collaborators, submitted to a remote compute backend through Microsoft services - or compiled by the local QDK simulator. +"""This python module stores template Q# strings that can be used to have Q# +code files ready to be shared with collaborators, submitted to a remote compute +backend through Microsoft services or compiled by the local QDK simulator. """ _header = '''namespace MyNamespace diff --git a/qsdk/backendbuddy/qpu_connection/honeywell_connection.py b/qsdk/backendbuddy/qpu_connection/honeywell_connection.py index 3a1f5221b..164c92fad 100644 --- a/qsdk/backendbuddy/qpu_connection/honeywell_connection.py +++ b/qsdk/backendbuddy/qpu_connection/honeywell_connection.py @@ -12,13 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" - Python wrappers around Honeywell REST API, to facilitate job submission, result retrieval and post-processing +"""Python wrappers around Honeywell REST API, to facilitate job submission, +result retrieval and post-processing. - Using Honeywell services require logins to their portal: https://um.qapi.honeywell.com/index.html - Users are expected to set the environment variables HONEYWELL_EMAIL, HONEYWELL_PASSWORD with their credentials +Using Honeywell services require logins to their portal: +https://um.qapi.honeywell.com/index.html +Users are expected to set the environment variables HONEYWELL_EMAIL, +HONEYWELL_PASSWORD with their credentials. - The portal above provides access to a dashboard, which is better suited for job monitoring experiments. +The portal above provides access to a dashboard, which is better suited for job +monitoring experiments. """ import os @@ -33,7 +36,9 @@ class HoneywellConnection(QpuConnection): - """ Wrapper about the Honeywell REST API, to facilitate job submission and automated post-processing of results """ + """Wrapper about the Honeywell REST API, to facilitate job submission and + automated post-processing of results. + """ def __init__(self): self.endpoint = "https://qapi.honeywell.com" + "/v1/" # Update endpoint or version number here if needed @@ -46,10 +51,10 @@ def header(self): return {"Content-Type": "application/json", "Authorization": self.id_token} def _login(self): + """Use Honeywell logins (email, password) to retrieve the id token to be + used for request with their REST API. Assumes users have set + HONEYWELL_EMAIL and HONEYWELL_PASSWORD in their environment variables. """ - Use Honeywell logins (email, password) to retrieve the id token to be used for request with their REST API. - Assumes users have set HONEYWELL_EMAIL and HONEYWELL_PASSWORD in their environment variables. - """ honeywell_email, honeywell_password = os.getenv("HONEYWELL_EMAIL", None), os.getenv("HONEYWELL_PASSWORD", None) if not (honeywell_email and honeywell_password): @@ -63,13 +68,17 @@ def _login(self): self.id_token, self.refresh_token = login_response["id-token"], login_response["refresh-token"] def _catch_request_error(self, return_dict): - """ Use the dictionary returned from a REST request to check for errors at runtime, and catch them """ + """Use the dictionary returned from a REST request to check for errors + at runtime, and catch them. + """ if "error" in return_dict: pprint.pprint(return_dict) raise RuntimeError(f"Error returned by Honeywell API :\n{return_dict['error']}") def get_devices(self): - """ Return dictionary of available devices to the user, as well as some useful information about them """ + """Return dictionary of available devices to the user, as well as some + useful information about them. + """ device_list_response = rq.get(self.endpoint + "/machine?config=true", headers=self.header) available_devices = json.loads(device_list_response.text) @@ -86,16 +95,19 @@ def get_devices(self): return device_info def job_submit(self, machine_name, qasm_circuit, n_shots, job_name, **job_specs): - """ Submit job, return job ID. + """Submit job, return job ID. Args: - machine_name (str): name of the target device - qasm_circuit (str): openqasm 2.0 string representing the quantum circuit - n_shots (int): number of shots - job_name (str): name to make the job more identifiable - **job_specs: extra arguments such as `max_cost` or `options` in the code below + machine_name (str): name of the target device. + qasm_circuit (str): openqasm 2.0 string representing the quantum + circuit. + n_shots (int): number of shots. + job_name (str): name to make the job more identifiable. + **job_specs: extra arguments such as `max_cost` or `options` in the + code below. + Returns: - job_id (str): alphanumeric character string representing the job id + str: alphanumeric character string representing the job id. """ # Honeywell does not support openqasm comments: remove them before submission @@ -119,12 +131,13 @@ def job_submit(self, machine_name, qasm_circuit, n_shots, job_name, **job_specs) return job_request['job'] def job_get_info(self, job_id): - """ Returns information about the job corresponding to the input job id + """Returns information about the job corresponding to the input job id. Args: - job_id (str): alphanumeric character string representing the job id + job_id (str): alphanumeric character string representing the job id. + Returns: - job_status (dict): status response from the Honeywell REST API + dict: status response from the Honeywell REST API. """ option = "?websocket=true" @@ -136,12 +149,14 @@ def job_get_info(self, job_id): return job_status def job_get_results(self, job_id): - """ Blocking call querying the REST API at a given frequency, until job results are available. + """Blocking call querying the REST API at a given frequency, until job + results are available. Args: - job_id (str): alphanumeric character string representing the job id + job_id (str): alphanumeric character string representing the job id. + Returns: - job_status (dict): status response from the Honeywell REST API + dict: status response from the Honeywell REST API. """ # The only way to retrieve the results, see if a job submission was incorrect, etc, is to look at the job info @@ -158,10 +173,11 @@ def job_get_results(self, job_id): raise RuntimeError(f'Unexpected job status :: \n {job_status}') def job_cancel(self, job_id): - """ Blocking call querying the REST API at a given frequency, until job results are available. + """Blocking call querying the REST API at a given frequency, until job + results are available. Args: - job_id (str): alphanumeric character string representing the job id + str: alphanumeric character string representing the job id. """ job_cancel = rq.post(self.endpoint+"/job/"+job_id+"/cancel", headers=self.header, data={}) job_cancel = json.loads(job_cancel.text) diff --git a/qsdk/backendbuddy/qpu_connection/ionq_connection.py b/qsdk/backendbuddy/qpu_connection/ionq_connection.py index 708ef8ee1..5bfe7d42a 100644 --- a/qsdk/backendbuddy/qpu_connection/ionq_connection.py +++ b/qsdk/backendbuddy/qpu_connection/ionq_connection.py @@ -12,15 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" - Python wrappers around IonQ REST API, to facilitate job submission, result retrieval and post-processing +"""Python wrappers around IonQ REST API, to facilitate job submission, result +retrieval and post-processing - Using IonQ services requires an API key. - Users are expected to set the environment variable IONQ_APIKEY with the value of this token. +Using IonQ services requires an API key. +Users are expected to set the environment variable IONQ_APIKEY with the value of +this token. - Please check IonQ documentations to ensure what features and acceptable values exist: - https://dewdrop.ionq.co/ - https://docs.ionq.co +Please check IonQ documentations to ensure what features and acceptable values +exist: +https://dewdrop.ionq.co/ +https://docs.ionq.co """ import os @@ -34,7 +36,9 @@ class IonQConnection(QpuConnection): - """ Wrapper about the IonQ REST API, to facilitate job submission and automated post-processing of results """ + """Wrapper about the IonQ REST API, to facilitate job submission and + automated post-processing of results. + """ def __init__(self): self.endpoint = "https://api.ionq.co" + "/v0.1" # Update endpoint or version number here if needed @@ -43,7 +47,7 @@ def __init__(self): @property def header(self): - """ Produce the header for REST requests """ + """Produce the header for REST requests.""" return {"Content-Type": "application/json", "Authorization": self.api_key} def _login(self): @@ -63,13 +67,17 @@ def _login(self): raise RuntimeError(f"{err}") def _catch_request_error(self, return_dict): - """ Use the dictionary returned from a REST request to check for errors at runtime, and catch them """ + """Use the dictionary returned from a REST request to check for errors + at runtime, and catch them. + """ if "error" in return_dict: pprint.pprint(return_dict) raise RuntimeError(f"Error returned by IonQ API :\n{return_dict['error']}") def _get_job_dataframe(self, job_history): - """ Display main job info as pandas dataframe. Takes REST request answer as input """ + """Display main job info as pandas dataframe. Takes REST request answer + as input. + """ jl = job_history['jobs'] jl_info = [(j['id'], j['status'], j['target']) for j in jl] @@ -77,17 +85,22 @@ def _get_job_dataframe(self, job_history): return jobs_df def job_submit(self, target_backend, ionq_circuit, n_shots, job_name, **job_specs): - """ Submit job, return job ID. + """Submit job, return job ID. Args: - target_backend (str): name of target device. See IonQ documentation for possible values. - Current acceptable values are 'simulator' and 'qpu' - ionq_circuit (str): Circuit in a compatible format (json format recommended) - n_shots (int): number of shots (ignored if target_backend is set to `simulator` - job_name (str): name to make the job more identifiable - **job_specs: extra arguments such as `lang` in the code below; `metadata` is not currently supported. + target_backend (str): name of target device. See IonQ documentation + for possible values. Current acceptable values are 'simulator' + and 'qpu'. + ionq_circuit (str): Circuit in a compatible format (json format + recommended). + n_shots (int): number of shots (ignored if target_backend is set to + `simulator`. + job_name (str): name to make the job more identifiable. + **job_specs: extra arguments such as `lang` in the code below; + `metadata` is not currently supported. + Returns: - job_id (str): string representing the job id + str: string representing the job id. """ payload = {"target": target_backend, @@ -105,12 +118,13 @@ def job_submit(self, target_backend, ionq_circuit, n_shots, job_name, **job_spec return return_dict['id'] def job_get_history(self): - """ Returns information about the job corresponding to the input job id + """Returns information about the job corresponding to the input job id. Args: - job_id (str): alphanumeric character string representing the job id + job_id (str): alphanumeric character string representing the job id. + Returns: - job_status (dict): status response from the REST API + dict: status response from the REST API. """ job_history = rq.get(self.endpoint + '/jobs', headers=self.header) @@ -120,12 +134,13 @@ def job_get_history(self): return self._get_job_dataframe(return_dict) def job_get_info(self, job_id): - """ Returns information about the job corresponding to the input job id + """Returns information about the job corresponding to the input job id. Args: - job_id (str): string representing the job id + job_id (str): string representing the job id. + Returns: - job_status (dict): status response from the REST API + dict: status response from the REST API. """ job_status = rq.get(self.endpoint + "/jobs/" + job_id, headers=self.header) @@ -135,12 +150,14 @@ def job_get_info(self, job_id): return job_status def job_get_results(self, job_id): - """ Blocking call querying the REST API at a given frequency, until job results are available. + """Blocking call querying the REST API at a given frequency, until job + results are available. Args: - job_id (str): string representing the job id + job_id (str): string representing the job id. + Returns: - job_status (dict): status response from the REST API + dict: status response from the REST API. """ while True: @@ -153,12 +170,13 @@ def job_get_results(self, job_id): raise RuntimeError(f'Unexpected job status :: \n {job_status}') def job_cancel(self, job_id): - """ Cancel / delete a job from IonQ servers. + """Cancel / delete a job from IonQ servers. Args: - job_id (str): string representing the job id + job_id (str): string representing the job id. + Returns: - job_status (dict): status response from the REST API + dict: status response from the REST API. """ job_cancel = rq.delete(self.endpoint+"/jobs/"+job_id, headers=self.header) job_cancel = json.loads(job_cancel.text) diff --git a/qsdk/backendbuddy/qpu_connection/qemist_cloud_connection.py b/qsdk/backendbuddy/qpu_connection/qemist_cloud_connection.py index b279404de..88738cc6f 100644 --- a/qsdk/backendbuddy/qpu_connection/qemist_cloud_connection.py +++ b/qsdk/backendbuddy/qpu_connection/qemist_cloud_connection.py @@ -12,11 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" - Python wrappers facilitating quantum experiment submission, monitoring and post-processing, through QEMIST Cloud. +"""Python wrappers facilitating quantum experiment submission, monitoring and +post-processing, through QEMIST Cloud. - Users are expected to set the environment variables QEMIST_AUTH_TOKEN and QEMIST_PROJECT_ID with values - retrieved from their QEMIST Cloud dashboard. +Users are expected to set the environment variables QEMIST_AUTH_TOKEN and +QEMIST_PROJECT_ID with values retrieved from their QEMIST Cloud dashboard. """ try: @@ -26,15 +26,16 @@ def job_submit(circuit, n_shots, backend): - """ Job submission to run a circuit on quantum hardware. + """Job submission to run a circuit on quantum hardware. Args: - circuit: a quantum circuit in the abstract format - n_shots (int): the number of shots - backend (str): the identifier string for the desired backend + circuit: a quantum circuit in the abstract format. + n_shots (int): the number of shots. + backend (str): the identifier string for the desired backend. Returns: - job_id (int): A problem handle / job ID that can be used to retrieve the result or cancel the problem. + int: A problem handle / job ID that can be used to retrieve the result + or cancel the problem. """ # Serialize circuit data @@ -51,27 +52,28 @@ def job_submit(circuit, n_shots, backend): def job_status(qemist_cloud_job_id): - """ Returns the current status of the problem, as a string. - Possible values: ready, in_progress, complete, cancelled. + """Returns the current status of the problem, as a string. Possible values: + ready, in_progress, complete, cancelled. - Args: - qemist_cloud_job_id (int): problem handle / job identifier + Args: + qemist_cloud_job_id (int): problem handle / job identifier. - Returns: - status (str): current status of the problem, as a string + Returns: + str: current status of the problem, as a string. """ return util.get_problem_status(qemist_cloud_job_id) def job_cancel(qemist_cloud_job_id): - """ Cancels the job matching the input job id, if done in time before it starts. - Returns a list of cancelled problems and number of subproblems, if any. + """Cancels the job matching the input job id, if done in time before it + starts. Returns a list of cancelled problems and number of subproblems, if + any. - Args: - qemist_cloud_job_id (int): problem handle / job identifier + Args: + qemist_cloud_job_id (int): problem handle / job identifier. - Returns: - res (dict): cancelled problems / subproblems + Returns: + dict: cancelled problems / subproblems. """ res = util.cancel_problems(qemist_cloud_job_id) @@ -81,16 +83,16 @@ def job_cancel(qemist_cloud_job_id): def job_result(qemist_cloud_job_id): - """ Blocks until the job results are available. - Returns a tuple containing the histogram of frequencies, and also the more in-depth raw data from - the cloud services provider as a nested dictionary + """Blocks until the job results are available. Returns a tuple containing + the histogram of frequencies, and also the more in-depth raw data from the + cloud services provider as a nested dictionary - Args: - qemist_cloud_job_id (int): problem handle / job identifier + Args: + qemist_cloud_job_id (int): problem handle / job identifier. - Returns: - freqs (dict): histogram of measurement frequencies - raw_data (dict): cloud provider raw data coming out as as nested dictionary + Returns: + dict: Histogram of measurement frequencies. + dict): The cloud provider raw data. """ try: @@ -124,23 +126,22 @@ def job_result(qemist_cloud_job_id): def job_estimate(circuit, n_shots): - """ - Returns an estimate of the cost of running an experiment. Some service providers care about - the complexity / structure of the input quantum circuit, some do not. + """Returns an estimate of the cost of running an experiment. Some service + providers care about the complexity / structure of the input quantum + circuit, some do not. - Some backends may charge per minute (such as simulators), which is difficult to estimate - and may be misleading. They are currently not included. + Some backends may charge per minute (such as simulators), which is difficult + to estimate and may be misleading. They are currently not included. - Braket prices: https://aws.amazon.com/braket/pricing/ - Azure Quantum prices: TBD + Braket prices: https://aws.amazon.com/braket/pricing/ + Azure Quantum prices: TBD Args: circuit (Circuit): the abstract circuit to be run on the target device. n_shots (int): number of shots in the expriment. Returns: - A dictionary of floating-point values (prices) in USD. - + dict: A dictionary of floating-point values (prices) in USD. """ # Compute prices for each available backend (see provider formulas) diff --git a/qsdk/backendbuddy/qpu_connection/qpu_connection.py b/qsdk/backendbuddy/qpu_connection/qpu_connection.py index cb5837f2d..61c9af24b 100644 --- a/qsdk/backendbuddy/qpu_connection/qpu_connection.py +++ b/qsdk/backendbuddy/qpu_connection/qpu_connection.py @@ -12,13 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" Abstract parent class encapsulating basic features of compute services for quantum circuit simulation """ +"""Abstract parent class encapsulating basic features of compute services for +quantum circuit simulation. +""" import abc class QpuConnection(abc.ABC): - """ Abstract class encapsulating login/authentication setup and job submission and management """ + """Abstract class encapsulating login/authentication setup and job + submission and management. + """ @abc.abstractmethod def __init__(self): @@ -26,20 +30,22 @@ def __init__(self): @abc.abstractmethod def job_submit(self): - """ Submit a job to the compute services """ + """Submit a job to the compute services.""" pass @abc.abstractmethod def job_get_info(self, job_id): - """ Retrieve information about a previously submitted job, through its job id """ + """Retrieve information about a previously submitted job, through its + job id. + """ pass @abc.abstractmethod def job_get_results(self, job_id): - """ Retrieve the results of previously submitted job, through its job id """ + """Retrieve the results of previously submitted job, through its job id.""" pass @abc.abstractmethod def job_cancel(self, job_id): - """ Attempt to cancel a previously submitted job, through its job id """ + """Attempt to cancel a previously submitted job, through its job id.""" pass diff --git a/qsdk/backendbuddy/simulator.py b/qsdk/backendbuddy/simulator.py index 8b6b8e3ab..0b6639cf6 100644 --- a/qsdk/backendbuddy/simulator.py +++ b/qsdk/backendbuddy/simulator.py @@ -12,19 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" - Simulator class, wrapping around the various simulators and abstracting their differences from the user. - Able to run noiseless and noisy simulations, leveraging the capabilities of different backends, quantum or - classical. - - If the user provides a noise model, then a noisy simulation is run with n_shots shots. - If the user only provides n_shots, a noiseless simulation is run, drawing the desired amount of shots. - If the target backend has access to the statevector representing the quantum state, we leverage any kind of - emulation available to reduce runtime (directly generating shot values from final statevector etc) - If the quantum circuit contains a MEASURE instruction, it is assumed to simulate a mixed-state and the simulation - will be carried by simulating individual shots (e.g a number of shots is required). - - Some backends may only support a subset of the above. This information is contained in a separate data-structure +"""Simulator class, wrapping around the various simulators and abstracting their +differences from the user. Able to run noiseless and noisy simulations, +leveraging the capabilities of different backends, quantum or classical. + +If the user provides a noise model, then a noisy simulation is run with n_shots +shots. If the user only provides n_shots, a noiseless simulation is run, drawing +the desired amount of shots. If the target backend has access to the statevector +representing the quantum state, we leverage any kind of emulation available to +reduce runtime (directly generating shot values from final statevector etc) If +the quantum circuit contains a MEASURE instruction, it is assumed to simulate a +mixed-state and the simulation will be carried by simulating individual shots +(e.g a number of shots is required). + +Some backends may only support a subset of the above. This information is +contained in a separate data-structure. """ import os @@ -53,14 +55,15 @@ class Simulator: def __init__(self, target=default_simulator, n_shots=None, noise_model=None): - """ - Instantiate Simulator object. - - Args: - target (str): One of the available target backends (quantum or classical). - The default simulator is qulacs if found in user's environment, otherwise cirq. - n_shots (int): Number of shots if using a shot-based simulator - noise_model: A noise model object assumed to be in the format expected from the target backend + """Instantiate Simulator object. + + Args: + target (str): One of the available target backends (quantum or + classical). The default simulator is qulacs if found in user's + environment, otherwise cirq. + n_shots (int): Number of shots if using a shot-based simulator. + noise_model: A noise model object assumed to be in the format + expected from the target backend. """ self._source = "abstract" self._target = target if target else default_simulator @@ -84,21 +87,27 @@ def __init__(self, target=default_simulator, n_shots=None, noise_model=None): raise ValueError("A number of shots needs to be specified.") def simulate(self, source_circuit, return_statevector=False, initial_statevector=None): - """ - Perform state preparation corresponding to the input circuit on the target backend, return the - frequencies of the different observables, and either the statevector or None depending on - the availability of the statevector and if return_statevector is set to True. - For the statevector backends supporting it, an initial statevector can be provided to initialize the - quantum state without simulating all the eauivalent gates. - - Args: - source_circuit: a circuit in the abstract format to be translated for the target backend - return_statevector(bool): option to return the statevector as well, if available - initial_statevector(list/array) : A valid statevector in the format supported by the target backend - - Returns: - A tuple containing a dictionary mapping multi-qubit states to their corresponding frequency, and - the statevector, if available for the target backend and requested by the user (if not, set to None). + """Perform state preparation corresponding to the input circuit on the + target backend, return the frequencies of the different observables, and + either the statevector or None depending on the availability of the + statevector and if return_statevector is set to True. For the + statevector backends supporting it, an initial statevector can be + provided to initialize the quantum state without simulating all the + equivalent gates. + + Args: + source_circuit: a circuit in the abstract format to be translated + for the target backend. + return_statevector(bool): option to return the statevector as well, + if available. + initial_statevector(list/array) : A valid statevector in the format + supported by the target backend. + + Returns: + dict: A dictionary mapping multi-qubit states to their corresponding + frequency. + numpy.array: The statevector, if available for the target backend + and requested by the user (if not, set to None). """ if source_circuit.is_mixed_state and not self.n_shots: @@ -187,7 +196,7 @@ def simulate(self, source_circuit, return_statevector=False, initial_statevector # Noiseless simulation using the statevector simulator otherwise else: - backend = qiskit.Aer.get_backend("aer_simulator", method='statevector') + backend = qiskit.Aer.get_backend("aer_simulator", method='statevector') save_state_circuit = qiskit.QuantumCircuit(source_circuit.width, source_circuit.width) save_state_circuit.save_statevector() translated_circuit = translated_circuit.compose(save_state_circuit) @@ -251,21 +260,24 @@ def simulate(self, source_circuit, return_statevector=False, initial_statevector return (frequencies, np.array(self._current_state)) if return_statevector else (frequencies, None) def get_expectation_value(self, qubit_operator, state_prep_circuit, initial_statevector=None): - r""" - Take as input a qubit operator H and a quantum circuit preparing a state |\psi> - Return the expectation value <\psi | H | \psi> - - In the case of a noiseless simulation, if the target backend exposes the statevector - then it is used directly to compute expectation values, or draw samples if required. - In the case of a noisy simulator, or if the statevector is not available on the target backend, individual - shots must be run and the workflow is akin to what we would expect from an actual QPU. - - Args: - qubit_operator(openfermion-style QubitOperator class): a qubit operator - state_prep_circuit: an abstract circuit used for state preparation - - Returns: - The expectation value of this operator with regards to the state preparation + r"""Take as input a qubit operator H and a quantum circuit preparing a + state |\psi>. Return the expectation value <\psi | H | \psi>. + + In the case of a noiseless simulation, if the target backend exposes the + statevector then it is used directly to compute expectation values, or + draw samples if required. In the case of a noisy simulator, or if the + statevector is not available on the target backend, individual shots + must be run and the workflow is akin to what we would expect from an + actual QPU. + + Args: + qubit_operator(openfermion-style QubitOperator class): a qubit + operator. + state_prep_circuit: an abstract circuit used for state preparation. + + Returns: + complex: The expectation value of this operator with regards to the + state preparation. """ # Check if simulator supports statevector if initial_statevector is not None and not self.statevector_available: @@ -300,17 +312,20 @@ def get_expectation_value(self, qubit_operator, state_prep_circuit, initial_stat return exp_real if (exp_imag == 0.) else exp_real + 1.0j * exp_imag def _get_expectation_value_from_statevector(self, qubit_operator, state_prep_circuit, initial_statevector=None): - r""" - Take as input a qubit operator H and a state preparation returning a ket |\psi>. - Return the expectation value <\psi | H | \psi>, computed without drawing samples (statevector only) - Users should not be calling this function directly, please call "get_expectation_value" instead. - - Args: - qubit_operator(openfermion-style QubitOperator class): a qubit operator - state_prep_circuit: an abstract circuit used for state preparation (only pure states) - - Returns: - The expectation value of this operator with regards to the state preparation + r"""Take as input a qubit operator H and a state preparation returning a + ket |\psi>. Return the expectation value <\psi | H | \psi>, computed + without drawing samples (statevector only). Users should not be calling + this function directly, please call "get_expectation_value" instead. + + Args: + qubit_operator(openfermion-style QubitOperator class): a qubit + operator. + state_prep_circuit: an abstract circuit used for state preparation + (only pure states). + + Returns: + complex: The expectation value of this operator with regards to the + state preparation. """ n_qubits = state_prep_circuit.width @@ -378,16 +393,18 @@ def _get_expectation_value_from_statevector(self, qubit_operator, state_prep_cir return expectation_value def _get_expectation_value_from_frequencies(self, qubit_operator, state_prep_circuit, initial_statevector=None): - r""" - Take as input a qubit operator H and a state preparation returning a ket |\psi>. - Return the expectation value <\psi | H | \psi> computed using the frequencies of observable states. - - Args: - qubit_operator(openfermion-style QubitOperator class): a qubit operator - state_prep_circuit: an abstract circuit used for state preparation - - Returns: - The expectation value of this operator with regards to the state preparation + r"""Take as input a qubit operator H and a state preparation returning a + ket |\psi>. Return the expectation value <\psi | H | \psi> computed + using the frequencies of observable states. + + Args: + qubit_operator(openfermion-style QubitOperator class): a qubit + operator. + state_prep_circuit: an abstract circuit used for state preparation. + + Returns: + complex: The expectation value of this operator with regards to the + state preparation. """ n_qubits = state_prep_circuit.width if not self.statevector_available or state_prep_circuit.is_mixed_state or self._noise_model: @@ -419,15 +436,18 @@ def _get_expectation_value_from_frequencies(self, qubit_operator, state_prep_cir @staticmethod def get_expectation_value_from_frequencies_oneterm(term, frequencies): - """ - Return the expectation value of a single-term qubit-operator, given the result of a state-preparation - - Args: - term(openfermion-style QubitOperator object): a qubit operator, with only a single term - frequencies(dict): histogram of frequencies of measurements (assumed to be in lsq-first format) - - Returns: - The expectation value of this operator with regards to the state preparation + """Return the expectation value of a single-term qubit-operator, given + the result of a state-preparation. + + Args: + term(openfermion-style QubitOperator object): a qubit operator, with + only a single term. + frequencies(dict): histogram of frequencies of measurements (assumed + to be in lsq-first format). + + Returns: + complex: The expectation value of this operator with regards to the + state preparation. """ if not frequencies.keys(): @@ -450,18 +470,22 @@ def get_expectation_value_from_frequencies_oneterm(term, frequencies): return expectation_term def _statevector_to_frequencies(self, statevector): - """ - For a given statevector representing the quantum state of a qubit register, returns a sparse histogram - of the probabilities in the least-significant-qubit (lsq) -first order. - e.g the string '100' means qubit 0 measured in basis state |1>, and qubit 1 & 2 both measured in state |0> - - Args: - statevector(list or ndarray(complex)): an iterable 1D data-structure containing the amplitudes - - Returns: - A dictionary whose keys are bitstrings representing the multi-qubit states with the least significant - qubit first (e.g '100' means qubit 0 in state |1>, and qubit 1 and 2 in state |0>), and the associated - value is the corresponding frequency. Unless threshold=0., this dictionary will be sparse. + """For a given statevector representing the quantum state of a qubit + register, returns a sparse histogram of the probabilities in the + least-significant-qubit (lsq) -first order. e.g the string '100' means + qubit 0 measured in basis state |1>, and qubit 1 & 2 both measured in + state |0>. + + Args: + statevector(list or ndarray(complex)): an iterable 1D data-structure + containing the amplitudes. + + Returns: + dict: A dictionary whose keys are bitstrings representing the + multi-qubit states with the least significant qubit first (e.g + '100' means qubit 0 in state |1>, and qubit 1 and 2 in state + |0>), and the associated value is the corresponding frequency. + Unless threshold=0., this dictionary will be sparse. """ n_qubits = int(math.log2(len(statevector))) @@ -495,13 +519,17 @@ def _statevector_to_frequencies(self, statevector): return freqs_shots def __int_to_binstr(self, i, n_qubits): - """ Convert an integer into a bit string of size n_qubits, in the order specified for the state vector """ + """Convert an integer into a bit string of size n_qubits, in the order + specified for the state vector. + """ bs = bin(i).split('b')[-1] state_binstr = "0" * (n_qubits - len(bs)) + bs return state_binstr[::-1] if (self.statevector_order == "msq_first") else state_binstr def __int_to_binstr_lsq(self, i, n_qubits): - """ Convert an integer into a bit string of size n_qubits, in the least-significant qubit order""" + """Convert an integer into a bit string of size n_qubits, in the + least-significant qubit order. + """ bs = bin(i).split('b')[-1] state_binstr = "0" * (n_qubits - len(bs)) + bs return state_binstr[::-1] diff --git a/qsdk/backendbuddy/translator/translate_braket.py b/qsdk/backendbuddy/translator/translate_braket.py index 5700f6b06..c186b0f93 100644 --- a/qsdk/backendbuddy/translator/translate_braket.py +++ b/qsdk/backendbuddy/translator/translate_braket.py @@ -12,19 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" - Functions helping with quantum circuit format conversion between abstract format and Braket format +"""Functions helping with quantum circuit format conversion between abstract +format and Braket format - In order to produce an equivalent circuit for the target backend, it is necessary to account for: - - how the gate names differ between the source backend to the target backend - - how the order and conventions for some of the inputs to the gate operations may also differ +In order to produce an equivalent circuit for the target backend, it is necessary +to account for: +- how the gate names differ between the source backend to the target backend. +- how the order and conventions for some of the inputs to the gate operations + may also differ. """ def get_braket_gates(): - """ - Map gate name of the abstract format to the equivalent methods of the braket.circuits.Circuit class - API and supported gates: https://amazon-braket-sdk-python.readthedocs.io/en/latest/_apidoc/braket.circuits.circuit.html + """Map gate name of the abstract format to the equivalent methods of the + braket.circuits.Circuit class API and supported gates: + https://amazon-braket-sdk-python.readthedocs.io/en/latest/_apidoc/braket.circuits.circuit.html """ from braket.circuits import Circuit as BraketCircuit @@ -46,12 +48,14 @@ def get_braket_gates(): def translate_braket(source_circuit): - """ Take in an abstract circuit, return a quantum circuit object as defined in the Python Braket SDK + """Take in an abstract circuit, return a quantum circuit object as defined + in the Python Braket SDK. + + Args: + source_circuit: quantum circuit in the abstract format. - Args: - source_circuit: quantum circuit in the abstract format - Returns: - target_circuit (braket.circuits.Circuit): quantum circuit in Python Braket SDK format + Returns: + braket.circuits.Circuit: quantum circuit in Python Braket SDK format. """ from braket.circuits import Circuit as BraketCircuit diff --git a/qsdk/backendbuddy/translator/translate_cirq.py b/qsdk/backendbuddy/translator/translate_cirq.py index ce31961fd..fd5f20a88 100644 --- a/qsdk/backendbuddy/translator/translate_cirq.py +++ b/qsdk/backendbuddy/translator/translate_cirq.py @@ -12,19 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" - Functions helping with quantum circuit format conversion between abstract format and circ format +"""Functions helping with quantum circuit format conversion between abstract +format and circ format. - In order to produce an equivalent circuit for the target backend, it is necessary to account for: - - how the gate names differ between the source backend to the target backend - - how the order and conventions for some of the inputs to the gate operations may also differ +In order to produce an equivalent circuit for the target backend, it is +necessary to account for: +- how the gate names differ between the source backend to the target backend. +- how the order and conventions for some of the inputs to the gate operations + may also differ. """ def get_cirq_gates(): - """ - Map gate name of the abstract format to the equivalent methods of the cirq class - API and supported gates: https://quantumai.google/cirq/gates + """Map gate name of the abstract format to the equivalent methods of the + cirq class API and supported gates: https://quantumai.google/cirq/gates. """ import cirq @@ -44,14 +45,16 @@ def get_cirq_gates(): def translate_cirq(source_circuit, noise_model=None): - """ Take in an abstract circuit, return an equivalent cirq QuantumCircuit instance + """Take in an abstract circuit, return an equivalent cirq QuantumCircuit + instance. + + Args: + source_circuit: quantum circuit in the abstract format. - Args: - source_circuit: quantum circuit in the abstract format - Returns: - target_circuit: a corresponding cirq Circuit. Right now, the - structure is of LineQubit. It is possible in the - future that we may support NamedQubit or GridQubit + Returns: + cirq.Circuit: a corresponding cirq Circuit. Right now, the structure is + of LineQubit. It is possible in the future that we may support + NamedQubit or GridQubit. """ import cirq diff --git a/qsdk/backendbuddy/translator/translate_json_ionq.py b/qsdk/backendbuddy/translator/translate_json_ionq.py index 6b34cf6d2..b35162dea 100644 --- a/qsdk/backendbuddy/translator/translate_json_ionq.py +++ b/qsdk/backendbuddy/translator/translate_json_ionq.py @@ -12,19 +12,22 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" - Functions helping with quantum circuit format conversion between abstract format and ionq format +"""Functions helping with quantum circuit format conversion between abstract +format and ionq format. - In order to produce an equivalent circuit for the target backend, it is necessary to account for: - - how the gate names differ between the source backend to the target backend - - how the order and conventions for some of the inputs to the gate operations may also differ +In order to produce an equivalent circuit for the target backend, it is +necessary to account for: +- how the gate names differ between the source backend to the target backend. +- how the order and conventions for some of the inputs to the gate operations + may also differ. """ def get_ionq_gates(): - """ - Map gate name of the abstract format to the equivalent gate name used in the json IonQ format - For more information: https://dewdrop.ionq.co/ https://docs.ionq.co + """Map gate name of the abstract format to the equivalent gate name used in + the json IonQ format. For more information: + - https://dewdrop.ionq.co/ + - https://docs.ionq.co """ GATE_JSON_IONQ = dict() @@ -34,13 +37,16 @@ def get_ionq_gates(): def translate_json_ionq(source_circuit): - """ Take in an abstract circuit, return a dictionary following the IonQ JSON format as described below - https://dewdrop.ionq.co/#json-specification + """Take in an abstract circuit, return a dictionary following the IonQ JSON + format as described below. + https://dewdrop.ionq.co/#json-specification + + Args: + source_circuit: quantum circuit in the abstract format. - Args: - source_circuit: quantum circuit in the abstract format - Returns: - json_ionq_circ (dict): representation of the quantum circuit following the IonQ JSON format + Returns: + dict: representation of the quantum circuit following the IonQ JSON + format. """ GATE_JSON_IONQ = get_ionq_gates() diff --git a/qsdk/backendbuddy/translator/translate_openqasm.py b/qsdk/backendbuddy/translator/translate_openqasm.py index 9ff8e3b7c..b29af009d 100644 --- a/qsdk/backendbuddy/translator/translate_openqasm.py +++ b/qsdk/backendbuddy/translator/translate_openqasm.py @@ -12,13 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" - Functions helping with quantum circuit format conversion between abstract format and - a subset of the openqasm format (the format generated by IBM openqasm functionalities in qiskit) - - In order to produce an equivalent circuit for the target backend, it is necessary to account for: - - how the gate names differ between the source backend to the target backend - - how the order and conventions for some of the inputs to the gate operations may also differ +"""Functions helping with quantum circuit format conversion between abstract +format and a subset of the openqasm format (the format generated by IBM openqasm +functionalities in qiskit). + +In order to produce an equivalent circuit for the target backend, it is +necessary to account for: +- how the gate names differ between the source backend to the target backend. +- how the order and conventions for some of the inputs to the gate operations + may also differ. """ import re @@ -27,11 +29,11 @@ def get_openqasm_gates(): - """ - Map gate name of the abstract format to the equivalent gate name used in openqasm - OpenQASM is a general format that allows users to express a quantum program, define conditional operations - manipulating quantum and qubit registers, as well as defining new quantum unitaries. - We however make the choice here to support well-known gate operations. + """Map gate name of the abstract format to the equivalent gate name used in + openqasm OpenQASM is a general format that allows users to express a quantum + program, define conditional operations manipulating quantum and qubit + registers, as well as defining new quantum unitaries. We however make the + choice here to support well-known gate operations. """ GATE_OPENQASM = dict() @@ -45,12 +47,14 @@ def get_openqasm_gates(): def translate_openqasm(source_circuit): - """ Take in an abstract circuit, return a OpenQASM 2.0 string using IBM Qiskit (they are the reference for OpenQASM) + """Take in an abstract circuit, return a OpenQASM 2.0 string using IBM + Qiskit (they are the reference for OpenQASM). - Args: - source_circuit: quantum circuit in the abstract format - Returns: - openqasm_string(str): the corresponding OpenQASM program, as per IBM Qiskit + Args: + source_circuit: quantum circuit in the abstract format. + + Returns: + str: the corresponding OpenQASM program, as per IBM Qiskit. """ from .translate_qiskit import translate_qiskit @@ -58,17 +62,22 @@ def translate_openqasm(source_circuit): def _translate_openqasm2abs(openqasm_str): - """ Take an OpenQASM 2.0 string as input (as defined by IBM Qiskit), return the equivalent abstract circuit. - Only a subset of OpenQASM supported, mostly to be able to go back and forth QASM and abstract representations - to leverage tools and innovation implemented to work in the QASM format. Not designed to support elaborate - QASM programs defining their own operations. Compatible with qiskit.QuantumCircuit.from_qasm method. - - Assumes single-qubit measurement instructions only. Final qubit register measurement is implicit. - - Args: - openqasm_string(str): an OpenQASM program, as a string, as defined by IBM Qiskit - Returns: - abs_circ: corresponding quantum circuit in the abstract format + """Take an OpenQASM 2.0 string as input (as defined by IBM Qiskit), return + the equivalent abstract circuit. Only a subset of OpenQASM supported, mostly + to be able to go back and forth QASM and abstract representations to + leverage tools and innovation implemented to work in the QASM format. Not + designed to support elaborate QASM programs defining their own operations. + Compatible with qiskit.QuantumCircuit.from_qasm method. + + Assumes single-qubit measurement instructions only. Final qubit register + measurement is implicit. + + Args: + openqasm_string(str): an OpenQASM program, as a string, as defined by + IBM Qiskit. + + Returns: + Circuit: corresponding quantum circuit in the abstract format. """ def parse_param(s): diff --git a/qsdk/backendbuddy/translator/translate_projectq.py b/qsdk/backendbuddy/translator/translate_projectq.py index 131cec90c..e1de3d08e 100644 --- a/qsdk/backendbuddy/translator/translate_projectq.py +++ b/qsdk/backendbuddy/translator/translate_projectq.py @@ -12,12 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" - Functions helping with quantum circuit format conversion between abstract format and projectq format - - In order to produce an equivalent circuit for the target backend, it is necessary to account for: - - how the gate names differ between the source backend to the target backend - - how the order and conventions for some of the inputs to the gate operations may also differ +"""Functions helping with quantum circuit format conversion between abstract +format and projectq format + +In order to produce an equivalent circuit for the target backend, it is +necessary to account for: +- how the gate names differ between the source backend to the target backend. +- how the order and conventions for some of the inputs to the gate operations + may also differ. """ import re @@ -25,9 +27,9 @@ def get_projectq_gates(): - """ - Map gate name of the abstract format to the equivalent gate name used in projectq - API and supported gates: https://projectq.readthedocs.io/en/latest/projectq.ops.html + """Map gate name of the abstract format to the equivalent gate name used in + projectq API and supported gates: + https://projectq.readthedocs.io/en/latest/projectq.ops.html """ GATE_PROJECTQ = dict() @@ -41,12 +43,15 @@ def get_projectq_gates(): def translate_projectq(source_circuit): - """ Take in an abstract circuit, return a string containing equivalent projectq instructions + """Take in an abstract circuit, return a string containing equivalent + projectq instructions. - Args: - source_circuit: quantum circuit in the abstract format - Returns: - projectq_circuit(str): the corresponding projectq instructions (allocation , gates, deallocation) + Args: + source_circuit: quantum circuit in the abstract format. + + Returns: + str: the corresponding projectq instructions (allocation , gates, + deallocation). """ GATE_PROJECTQ = get_projectq_gates() @@ -69,16 +74,18 @@ def translate_projectq(source_circuit): def _translate_projectq2abs(projectq_str): - """ - Convenience function to help people move away from their ProjectQ workflow. - Take ProjectQ instructions, expressed as a string, similar to one from the ProjectQ `CommandPrinter` engine. - Will bundle all qubit allocation (resp. deallocation) at the beginning (resp. end) of the circuit. - Example available in the `examples` folder. - - Args: - projectq_str(str): ProjectQ program, as a string (Allocate, Deallocate, gate operations...) - Returns: - abs_circ: corresponding quantum circuit in the abstract format + """Convenience function to help people move away from their ProjectQ + workflow. Take ProjectQ instructions, expressed as a string, similar to one + from the ProjectQ `CommandPrinter` engine. Will bundle all qubit allocation + (resp. deallocation) at the beginning (resp. end) of the circuit. Example + available in the `examples` folder. + + Args: + projectq_str(str): ProjectQ program, as a string (Allocate, Deallocate, + gate operations...). + + Returns: + Circuit: corresponding quantum circuit in the abstract format. """ # Get dictionary of gate mapping, as the reverse dictionary of abs -> projectq translation diff --git a/qsdk/backendbuddy/translator/translate_qdk.py b/qsdk/backendbuddy/translator/translate_qdk.py index a08dd56af..5846624bf 100644 --- a/qsdk/backendbuddy/translator/translate_qdk.py +++ b/qsdk/backendbuddy/translator/translate_qdk.py @@ -12,19 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" - Functions helping with quantum circuit format conversion between abstract format and qdk/qsharp format +"""Functions helping with quantum circuit format conversion between abstract +format and qdk/qsharp format. - In order to produce an equivalent circuit for the target backend, it is necessary to account for: - - how the gate names differ between the source backend to the target backend - - how the order and conventions for some of the inputs to the gate operations may also differ +In order to produce an equivalent circuit for the target backend, it is +necessary to account for: +- how the gate names differ between the source backend to the target backend. +- how the order and conventions for some of the inputs to the gate operations + may also differ. """ def get_qdk_gates(): - """ - Map gate name of the abstract format to the equivalent gate name used in Q# operations - API and supported gates: https://docs.microsoft.com/en-us/qsharp/api/qsharp/microsoft.quantum.intrinsic + """Map gate name of the abstract format to the equivalent gate name used in + Q# operations API and supported gates: + https://docs.microsoft.com/en-us/qsharp/api/qsharp/microsoft.quantum.intrinsic """ GATE_QDK = dict() @@ -38,14 +40,17 @@ def get_qdk_gates(): def translate_qsharp(source_circuit, operation="MyQsharpOperation"): - """ Take in an abstract circuit, generate the corresponding Q# operation (state prep + measurement) string, - in the appropriate Q# template. The Q# output can be written to file and will be compiled at runtime. + """Take in an abstract circuit, generate the corresponding Q# operation + (state prep + measurement) string, in the appropriate Q# template. The Q# + output can be written to file and will be compiled at runtime. + + Args: + source_circuit: quantum circuit in the abstract format. + operation (str), optional: name of the Q# operation. - Args: - source_circuit: quantum circuit in the abstract format - operation (str), optional: name of the Q# operation - Returns: - The Q# code (operation + template). This needs to be written into a .qs file, and compiled at runtime + Returns: + str: The Q# code (operation + template). This needs to be written into a + .qs file, and compiled at runtime. """ GATE_QDK = get_qdk_gates() diff --git a/qsdk/backendbuddy/translator/translate_qiskit.py b/qsdk/backendbuddy/translator/translate_qiskit.py index 0411195fa..efb304ef1 100644 --- a/qsdk/backendbuddy/translator/translate_qiskit.py +++ b/qsdk/backendbuddy/translator/translate_qiskit.py @@ -12,19 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" - Functions helping with quantum circuit format conversion between abstract format and qiskit format +"""Functions helping with quantum circuit format conversion between abstract +format and qiskit format. - In order to produce an equivalent circuit for the target backend, it is necessary to account for: - - how the gate names differ between the source backend to the target backend - - how the order and conventions for some of the inputs to the gate operations may also differ +In order to produce an equivalent circuit for the target backend, it is +necessary to account for: +- how the gate names differ between the source backend to the target backend. +- how the order and conventions for some of the inputs to the gate operations + may also differ. """ def get_qiskit_gates(): - """ - Map gate name of the abstract format to the equivalent add_gate method of Qiskit's QuantumCircuit class - API and supported gates: https://qiskit.org/documentation/stubs/qiskit.circuit.QuantumCircuit.html + """Map gate name of the abstract format to the equivalent add_gate method of + Qiskit's QuantumCircuit class API and supported gates: + https://qiskit.org/documentation/stubs/qiskit.circuit.QuantumCircuit.html """ import qiskit @@ -45,12 +47,14 @@ def get_qiskit_gates(): def translate_qiskit(source_circuit): - """ Take in an abstract circuit, return an equivalent qiskit QuantumCircuit instance + """Take in an abstract circuit, return an equivalent qiskit QuantumCircuit + instance + + Args: + source_circuit: quantum circuit in the abstract format. - Args: - source_circuit: quantum circuit in the abstract format - Returns: - qiskit.QuantumCircuit: the corresponding qiskit quantum circuit + Returns: + qiskit.QuantumCircuit: the corresponding qiskit quantum circuit. """ import qiskit diff --git a/qsdk/backendbuddy/translator/translate_qulacs.py b/qsdk/backendbuddy/translator/translate_qulacs.py index 2d5d08afa..c0982ef09 100644 --- a/qsdk/backendbuddy/translator/translate_qulacs.py +++ b/qsdk/backendbuddy/translator/translate_qulacs.py @@ -12,19 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" - Functions helping with quantum circuit format conversion between abstract format and qulacs format +"""Functions helping with quantum circuit format conversion between abstract +format and qulacs format. - In order to produce an equivalent circuit for the target backend, it is necessary to account for: - - how the gate names differ between the source backend to the target backend - - how the order and conventions for some of the inputs to the gate operations may also differ +In order to produce an equivalent circuit for the target backend, it is +necessary to account for: +- how the gate names differ between the source backend to the target backend. +- how the order and conventions for some of the inputs to the gate operations + may also differ. """ def get_qulacs_gates(): - """ - Map gate name of the abstract format to the equivalent add_gate method of Qulacs's QuantumCircuit class - API and supported gates: http://qulacs.org/class_quantum_circuit.html + """Map gate name of the abstract format to the equivalent add_gate method of + Qulacs's QuantumCircuit class API and supported gates: + http://qulacs.org/class_quantum_circuit.html """ import qulacs @@ -44,15 +46,18 @@ def get_qulacs_gates(): def translate_qulacs(source_circuit, noise_model=None): - """ Take in an abstract circuit, return an equivalent qulacs QuantumCircuit instance - If provided with a noise model, will add noisy gates at translation. Not very useful to look at, as qulacs - does not provide much information about the noisy gates added when printing the "noisy circuit". + """Take in an abstract circuit, return an equivalent qulacs QuantumCircuit + instance. If provided with a noise model, will add noisy gates at + translation. Not very useful to look at, as qulacs does not provide much + information about the noisy gates added when printing the "noisy circuit". + + Args: + source_circuit: quantum circuit in the abstract format. + noise_model: A NoiseModel object from this package, located in the + noisy_simulation subpackage. - Args: - source_circuit: quantum circuit in the abstract format - noise_model: A NoiseModel object from this package, located in the noisy_simulation subpackage - Returns: - qulacs.QuantumCircuit: the corresponding qulacs quantum circuit + Returns: + qulacs.QuantumCircuit: the corresponding qulacs quantum circuit. """ import qulacs diff --git a/qsdk/helpers/utils.py b/qsdk/helpers/utils.py index d295a23d5..79081c5ad 100644 --- a/qsdk/helpers/utils.py +++ b/qsdk/helpers/utils.py @@ -12,9 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" - This file provides information about what the backends currently installed in the user's environment, - for the purpose of running / skipping tests, and setting a default simulator. +"""This file provides information about what the backends currently installed in +the user's environment, for the purpose of running / skipping tests, and setting +a default simulator. """ diff --git a/qsdk/problem_decomposition/dmet/_helpers/dmet_bath.py b/qsdk/problem_decomposition/dmet/_helpers/dmet_bath.py index 9d014211c..b0069be81 100644 --- a/qsdk/problem_decomposition/dmet/_helpers/dmet_bath.py +++ b/qsdk/problem_decomposition/dmet/_helpers/dmet_bath.py @@ -33,8 +33,8 @@ def dmet_fragment_bath(mol, t_list, temp_list, onerdm_low): calculation (float64). Returns: - bath_orb (numpy.array): The bath orbitals (float64). - e_core (numpy.array): Orbital energies (float64). + numpy.array: The bath orbitals (float64). + numpy.array: Orbital energies (float64). """ # Extract the one-particle RDM for the active space @@ -63,7 +63,7 @@ def dmet_onerdm_embed(mol, temp_list, onerdm_before): calculation (float64). Returns: - onerdm_temp3 (numpy.array): Extracted one-particle RDM (float64). + numpy.array: Extracted one-particle RDM (float64). """ # Get the number of orbitals @@ -97,8 +97,8 @@ def dmet_bath_orb_sort(t_list, e_before, c_before): (float64). Returns: - e_new (numpy.array): Sorted orbital energies (float64). - c_new (numpy.array): Coefficients of the sorted orbitals (float64). + numpy.array: Sorted orbital energies (float64). + numpy.array: Coefficients of the sorted orbitals (float64). """ # Sort the orbital energies (Occupation of 1.0 should come first...) @@ -132,8 +132,8 @@ def dmet_add_to_bath_orb( mol, t_list, temp_list, e_before, c_before ): frozen core (float64). Returns: - c_before (numpy.array): Constructed bath orbitals (float64). - e_occupied_core_orbitals (numpy.array): Orbital energies (float64). + numpy.array: Constructed bath orbitals (float64). + numpy.array: Orbital energies (float64). """ # Copy the bath orbitals and energies be fore adding the core diff --git a/qsdk/problem_decomposition/dmet/_helpers/dmet_fragment.py b/qsdk/problem_decomposition/dmet/_helpers/dmet_fragment.py index 7d04c9158..5d5daf687 100644 --- a/qsdk/problem_decomposition/dmet/_helpers/dmet_fragment.py +++ b/qsdk/problem_decomposition/dmet/_helpers/dmet_fragment.py @@ -31,10 +31,10 @@ def dmet_fragment_constructor(mol, atom_list, number_fragment): (int). Returns: - orb_list (list): The number of orbitals for each fragment (int). - orb_list2 (list): List of lists of the minimum and maximum orbital label - for each fragment (int). - atom_list2 (list): The new atom list for each fragment (int). + list: The number of orbitals for each fragment (int). + list: List of lists of the minimum and maximum orbital label for each + fragment (int). + list: The new atom list for each fragment (int). """ # Make a new atom list based on how many fragments for DMET calculation diff --git a/qsdk/problem_decomposition/dmet/_helpers/dmet_onerdm.py b/qsdk/problem_decomposition/dmet/_helpers/dmet_onerdm.py index dfe730dea..53eb59d04 100644 --- a/qsdk/problem_decomposition/dmet/_helpers/dmet_onerdm.py +++ b/qsdk/problem_decomposition/dmet/_helpers/dmet_onerdm.py @@ -29,8 +29,7 @@ def dmet_low_rdm(active_fock, number_active_electrons): number_active_electrons (int): Number of electrons in the entire system. Returns: - onerdm (numpy.array): One-particle RDM of the low-level calculation - (float64). + numpy.array: One-particle RDM of the low-level calculation (float64). """ # Extract the occupied part of the one-particle RDM @@ -54,10 +53,9 @@ def dmet_fragment_rdm(t_list, bath_orb, core_occupied, number_active_electrons): number_active_electrons (int): Number of electrons in the entire system. Returns: - number_orbitals (int): Number of orbitals for fragment calculation. - number_electrons (int): Number of electrons for fragment calulation. - core_occupied_onerdm (numpy.array): Core part of the one-particle RDM - (float64). + int: Number of orbitals for fragment calculation. + int: Number of electrons for fragment calulation. + numpy.array: Core part of the one-particle RDM (float64). """ # Obtain number of active orbitals diff --git a/qsdk/problem_decomposition/dmet/_helpers/dmet_orbitals.py b/qsdk/problem_decomposition/dmet/_helpers/dmet_orbitals.py index 0101da22e..42bc282d9 100644 --- a/qsdk/problem_decomposition/dmet/_helpers/dmet_orbitals.py +++ b/qsdk/problem_decomposition/dmet/_helpers/dmet_orbitals.py @@ -108,12 +108,11 @@ def dmet_fragment_hamiltonian(self, bath_orb, norb_high, onerdm_core): (float64). Returns: - frag_oneint (numpy.array): One-electron integrals for fragment - calculation (float64). - frag_fock (numpy.array): The fock matrix for fragment calculation + numpy.array: One-electron integrals for fragment calculation + (float64). + numpy.array: The fock matrix for fragment calculation (float64). + numpy.array: Two-electron integrals for fragment calculation (float64). - frag_twoint (numpy.array): Two-electron integrals for fragment - calculation (float64). """ # Calculate one-electron integrals diff --git a/qsdk/problem_decomposition/dmet/_helpers/dmet_scf.py b/qsdk/problem_decomposition/dmet/_helpers/dmet_scf.py index 955499012..438d695b5 100644 --- a/qsdk/problem_decomposition/dmet/_helpers/dmet_scf.py +++ b/qsdk/problem_decomposition/dmet/_helpers/dmet_scf.py @@ -36,12 +36,10 @@ def dmet_fragment_scf(t_list, two_ele, fock, number_electrons, number_orbitals, chemical_potential (float64): The chemical potential. Returns: - mf_frag (pyscf.scf.RHF): The mean field of the molecule (Fragment - calculation). - fock_frag_copy (numpy.array): The fock matrix with chemical potential - subtracted (float64). - mol_frag (pyscf.gto.Mole): The molecule to simulate (Fragment - calculation). + pyscf.scf.RHF: The mean field of the molecule (Fragment calculation). + numpy.array: The fock matrix with chemical potential subtracted + (float64). + pyscf.gto.Mole: The molecule to simulate (Fragment calculation). """ # Deep copy the fock matrix diff --git a/qsdk/problem_decomposition/dmet/_helpers/dmet_scf_guess.py b/qsdk/problem_decomposition/dmet/_helpers/dmet_scf_guess.py index 8815ab2ba..3c6bb3af9 100644 --- a/qsdk/problem_decomposition/dmet/_helpers/dmet_scf_guess.py +++ b/qsdk/problem_decomposition/dmet/_helpers/dmet_scf_guess.py @@ -36,7 +36,7 @@ def dmet_fragment_guess(t_list, bath_orb, chemical_potential, norb_high, number_ calculation (float64). Returns: - frag_guess (numpy.array): The guess orbitals (float64). + numpy.array: The guess orbitals (float64). """ # Construct the fock matrix of the fragment (subtract the chemical potential for consistency) diff --git a/qsdk/problem_decomposition/dmet/dmet_problem_decomposition.py b/qsdk/problem_decomposition/dmet/dmet_problem_decomposition.py index bc2158320..73737176b 100644 --- a/qsdk/problem_decomposition/dmet/dmet_problem_decomposition.py +++ b/qsdk/problem_decomposition/dmet/dmet_problem_decomposition.py @@ -54,8 +54,8 @@ class DMETProblemDecomposition(ProblemDecomposition): only a string is detected, this solver is used for all fragments. optimizer (function handle): A function defining the classical optimizer and its behavior. - initial_chemical_potential (str or array-like) : Initial value for the - chemical potential. + initial_chemical_potential (float) : Initial value for the chemical + potential. solvers_options (list or dict): List of dictionaries for the solver options. If only a single dictionary is passed, the same options are applied for every solver. This will raise an error if different @@ -573,9 +573,9 @@ def _default_optimizer(self, func, var_params): var_params (list): The variational parameters (float). Returns: - The optimal chemical potential found by the optimizer. + float: The chemical potential found by the optimizer. """ result = scipy.optimize.newton(func, var_params, tol=1e-5) - return result + return result.real diff --git a/qsdk/problem_decomposition/electron_localization/iao_localization.py b/qsdk/problem_decomposition/electron_localization/iao_localization.py index dbe26c96b..e511d7342 100644 --- a/qsdk/problem_decomposition/electron_localization/iao_localization.py +++ b/qsdk/problem_decomposition/electron_localization/iao_localization.py @@ -65,8 +65,7 @@ def _iao_occupied_orbitals(mol, mf): mf (pyscf.scf.RHF): The mean field of the molecule. Returns: - iao_active (numpy.array): The localized orbitals for the occupied space - (float64). + numpy.array: The localized orbitals for the occupied space (float64). """ # Get MO coefficient of occupied MOs @@ -115,7 +114,7 @@ def _iao_complementary_orbitals(mol, iao_ref): iao_ref (numpy.array): IAO in occupied space (float64). Returns: - iao_comp (numpy.array): IAO in complementary space (float64). + numpy.array: IAO in complementary space (float64). """ # Get the total number of AOs @@ -166,8 +165,8 @@ def _iao_count_active(mol, min_mol): min_mol (numpy.array): The molecule to simulate in minao basis. Returns: - number_active (int): Number of active orbitals. - active_number_list (list): List of active orbitals (int). + int: Number of active orbitals. + list: List of active orbitals (int). """ # Initialize the list @@ -189,13 +188,11 @@ def _iao_complementary_space(iao_ref, s, number_inactive): Args: iao_ref (numpy.array): IAO in occupied space. - (float64) s (numpy.array): The overlap matrix. - (float64) number_inactive (int): The number of inactive orbitals. Returns: - eigen_vectors (numpy.array): The inactive part in IAO (float64). + numpy.array: The inactive part in IAO (float64). """ # Construct the "density matrix" for active space @@ -220,7 +217,7 @@ def _iao_atoms(mol, iao1, iao2): iao2 (numpy.array): IAO for complementary space (float64). Returns: - iao_combined (numpy.array): The rearranged IAO (float64). + numpy.array: The rearranged IAO (float64). """ # Calclate the integrals for assignment @@ -260,7 +257,7 @@ def _dmet_atom_list(mol, orbitals): orbitals (numpy.array): Coordinates for the orbital centers (float64). Returns: - newlist (list): The list for atom assignment for IAO (int). + list: The list for atom assignment for IAO (int). """ # Initialize the list @@ -291,7 +288,7 @@ def _dmet_orb_list(mol, atom_list): atom_list (list): Atom list for IAO assignment (int). Returns: - newlist (list): The orbital list in new order (int). + list: The orbital list in new order (int). """ newlist = [] for i in range(mol.natm): diff --git a/qsdk/problem_decomposition/oniom/_helpers/helper_classes.py b/qsdk/problem_decomposition/oniom/_helpers/helper_classes.py index 663f68e96..782d70748 100644 --- a/qsdk/problem_decomposition/oniom/_helpers/helper_classes.py +++ b/qsdk/problem_decomposition/oniom/_helpers/helper_classes.py @@ -81,8 +81,7 @@ def __init__(self, solver_low, options_low=None, solver_high=None, options_high= self.e_fragment = None def build(self): - """Get the solver objects for this layer. Also defined molecule objects. - """ + """Get the solver objects for this layer. Also defined molecule objects.""" # Low accuracy solver. # We begin by defining the molecule. @@ -168,7 +167,7 @@ def get_solver(self, molecule, solver_string, options_solver): options_solver (dict): Options for the solver. Returns: - Solver object or string. + ElectronicStructureSolver: Solver object or string. """ if solver_string == "RHF": @@ -235,7 +234,8 @@ def relink(self, geometry): [[str,tuple(float,float,float)],...]. Returns: - Atomic species, and position of replacement atom. + str: Atomic species. + tuple: Position (x, y, z) of replacement atom. """ staying = np.array(geometry[self.staying][1]) diff --git a/qsdk/toolboxes/ansatz_generator/fermionic_operators.py b/qsdk/toolboxes/ansatz_generator/fermionic_operators.py index 578b4bce8..ada0228bc 100644 --- a/qsdk/toolboxes/ansatz_generator/fermionic_operators.py +++ b/qsdk/toolboxes/ansatz_generator/fermionic_operators.py @@ -36,7 +36,7 @@ def number_operator(n_orbs, up_then_down=False): openfermion (False). Returns: - num_op (FermionicOperator): The number operator penalty \hat{N}. + FermionicOperator: The number operator penalty \hat{N}. """ all_terms = number_operator_list(n_orbs, up_then_down) @@ -55,7 +55,7 @@ def number_operator_list(n_orbs, up_then_down=False): openfermion (False). Returns: - all_terms (list): The number operator penalty \hat{N}. + list: The number operator penalty \hat{N}. """ all_terms = list() @@ -75,7 +75,7 @@ def spinz_operator(n_orbs, up_then_down=False): openfermion (False). Returns: - spin_op (FermionicOperator): The Sz operator \hat{Sz}. + FermionicOperator: The Sz operator \hat{Sz}. """ all_terms = spinz_operator_list(n_orbs, up_then_down) @@ -94,7 +94,7 @@ def spinz_operator_list(n_orbs, up_then_down=False): openfermion (False). Returns: - all_terms (list): The Sz operator \hat{Sz}. + list: The Sz operator \hat{Sz}. """ all_terms = list() @@ -115,7 +115,7 @@ def spin2_operator(n_orbs, up_then_down=False): openfermion (False). Returns: - spin2_op (FermionicOperator): The S^2 operator \hat{S}^2. + FermionicOperator: The S^2 operator \hat{S}^2. """ all_terms = spin2_operator_list(n_orbs, up_then_down) @@ -135,7 +135,7 @@ def spin2_operator_list(n_orbs, up_then_down=False): openfermion (False). Returns: - all_terms (list): The S^2 operator \hat{S}^2. + list: The S^2 operator \hat{S}^2. """ all_terms = list() diff --git a/qsdk/toolboxes/ansatz_generator/penalty_terms.py b/qsdk/toolboxes/ansatz_generator/penalty_terms.py index c1f1b1a85..63d55d2cd 100644 --- a/qsdk/toolboxes/ansatz_generator/penalty_terms.py +++ b/qsdk/toolboxes/ansatz_generator/penalty_terms.py @@ -42,7 +42,7 @@ def number_operator_penalty(n_orbs, n_electrons, mu=1, up_then_down=False): mapping handle the ordering. Returns: - (FermionicOperator): The number operator penalty term + FermionicOperator: The number operator penalty term mu*(\hat{N}-n_electrons)^2. """ @@ -65,7 +65,7 @@ def spin_operator_penalty(n_orbs, sz, mu=1, up_then_down=False): mapping handle the ordering. Returns: - (FermionicOperator): The Sz operator penalty term mu*(\hat{Sz}-sz)^2. + FermionicOperator: The Sz operator penalty term mu*(\hat{Sz}-sz)^2. """ all_terms = [[(), -sz]] + spinz_operator_list(n_orbs, up_then_down) @@ -91,7 +91,7 @@ def spin2_operator_penalty(n_orbs, s2, mu=1, up_then_down=False): mapping handle the ordering. Returns: - (FermionicOperator): The S^2 operator penalty term mu*(\hat{S}^2-s2)^2. + FermionicOperator: The S^2 operator penalty term mu*(\hat{S}^2-s2)^2. """ all_terms = [[(), -s2]] + spin2_operator_list(n_orbs, up_then_down) @@ -123,7 +123,7 @@ def combined_penalty(n_orbs, opt_penalty_terms=None, up_then_down=False): mapping handle this. Returns: - pen_ferm (FermionicOperator): The combined n_electron+sz+s^2 penalty + FermionicOperator: The combined n_electron+sz+s^2 penalty terms. """ diff --git a/qsdk/toolboxes/ansatz_generator/rucc.py b/qsdk/toolboxes/ansatz_generator/rucc.py index 4b3c6c5d4..2c2df90de 100644 --- a/qsdk/toolboxes/ansatz_generator/rucc.py +++ b/qsdk/toolboxes/ansatz_generator/rucc.py @@ -95,7 +95,7 @@ def prepare_reference_state(self): This method outputs |1010>. Returns: - qsdk.backendbuddy.Circuit: |1010> initial state. + Circuit: |1010> initial state. """ if self.reference_state_initialization not in self.supported_reference_state: @@ -111,8 +111,8 @@ def build_circuit(self, var_params=None): var_params). Args: - var_params (array-like): Initial variational parameters. Must be - consistent with the chosen UCC (1 or 3). + list: Initial variational parameters. Must be consistent with the + chosen UCC (1 or 3). """ # Set initial variational parameters used to build the circuit. @@ -141,8 +141,7 @@ def update_var_params(self, var_params): time if only the variational parameters change. Args: - var_params (array-like): Variational parameters to parse into the - circuit. + list: Variational parameters to parse into the circuit. """ assert len(var_params) == self.n_var_params @@ -157,7 +156,7 @@ def _ucc1(self): state are removed. Returns: - qsdk.backendbuddy.Circuit: UCC1 quantum circuit. + Circuit: UCC1 quantum circuit. """ # Initialization of an empty list. @@ -185,7 +184,7 @@ def _ucc3(self): state are removed. Returns: - qsdk.backendbuddy.Circuit: UCC3 quantum circuit. + Circuit: UCC3 quantum circuit. """ # Initialization of an empty list. diff --git a/qsdk/toolboxes/ansatz_generator/uccsd.py b/qsdk/toolboxes/ansatz_generator/uccsd.py index d9d970c6d..17cb7886f 100644 --- a/qsdk/toolboxes/ansatz_generator/uccsd.py +++ b/qsdk/toolboxes/ansatz_generator/uccsd.py @@ -201,8 +201,7 @@ def _get_singlet_qubit_operator(self): and translate to QubitOperator via relevant qubit mapping. Returns: - qubit_op (QubitOperator): qubit-encoded elements of the UCCSD - ansatz. + QubitOperator: qubit-encoded elements of the UCCSD ansatz. """ fermion_op = uccsd_singlet_generator(self.var_params, self.n_spinorbitals, self.n_electrons) qubit_op = fermion_to_qubit_mapping(fermion_operator=fermion_op, @@ -222,8 +221,7 @@ def _get_openshell_qubit_operator(self): parameters, and translate to QubitOperator via relevant qubit mapping. Returns: - qubit_op (QubitOperator): qubit-encoded elements of the UCCSD - ansatz. + QubitOperator: qubit-encoded elements of the UCCSD ansatz. """ fermion_op = uccsd_openshell_generator(self.var_params, self.n_spinorbitals, diff --git a/qsdk/toolboxes/ansatz_generator/upccgsd.py b/qsdk/toolboxes/ansatz_generator/upccgsd.py index 3d0931a7c..ce61ddbce 100644 --- a/qsdk/toolboxes/ansatz_generator/upccgsd.py +++ b/qsdk/toolboxes/ansatz_generator/upccgsd.py @@ -197,8 +197,8 @@ def _get_qubit_operator(self, current_k): parameters, and translate to QubitOperator via relevant qubit mapping. Returns: - qubit_op (QubitOperator): qubit-encoded elements of the UpCCGSD - ansatz for current_k. + QubitOperator: qubit-encoded elements of the UpCCGSD ansatz for + current_k. """ current_k_params = self.var_params[current_k*self.n_var_params_per_step:(current_k+1)*self.n_var_params_per_step] diff --git a/qsdk/toolboxes/ansatz_generator/variational_circuit.py b/qsdk/toolboxes/ansatz_generator/variational_circuit.py index 8db1e9990..6e25ea7dd 100644 --- a/qsdk/toolboxes/ansatz_generator/variational_circuit.py +++ b/qsdk/toolboxes/ansatz_generator/variational_circuit.py @@ -26,7 +26,7 @@ class VariationalCircuitAnsatz(Ansatz): enables users to provide a custom pre-built circuit. Args: - abstract_circuit (Circuit) : Circuit with variational gates. + Circuit: Circuit with variational gates. """ def __init__(self, abstract_circuit): diff --git a/qsdk/toolboxes/measurements/estimate_measurements.py b/qsdk/toolboxes/measurements/estimate_measurements.py index c0fce1543..62fd14381 100644 --- a/qsdk/toolboxes/measurements/estimate_measurements.py +++ b/qsdk/toolboxes/measurements/estimate_measurements.py @@ -12,30 +12,35 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" - This file provides functions allowing users to estimate the number of measurements that are needed to - approximate the expectation values of some qubit operators up to a given accuracy. +"""This file provides functions allowing users to estimate the number of +measurements that are needed to approximate the expectation values of some qubit +operators up to a given accuracy. - The functions range from simple general heuristics to more complex approaches that may leverage our knowledge - of the quantum state. +The functions range from simple general heuristics to more complex approaches +that may leverage our knowledge of the quantum state. """ import math def get_measurement_estimate(qb_op, digits=3, method="uniform"): - """ Given a qubit operator and a level of accuracy, computes the number of measurements required by each term - of the qubit operator to reach the accuracy provided by the user when computing expectation values, returns it - as a dictionary mapping measurement basis to number of measurements. + """Given a qubit operator and a level of accuracy, computes the number of + measurements required by each term of the qubit operator to reach the + accuracy provided by the user when computing expectation values, returns it + as a dictionary mapping measurement basis to number of measurements. - "uniform" method makes no assumption about the underlying probability distribution resulting from the quantum - state preparation circuit. The rule of thumb is "Multiply number of samples by 100 for each digit of accuracy". + "uniform" method makes no assumption about the underlying probability + distribution resulting from the quantum state preparation circuit. The rule + of thumb is "Multiply number of samples by 100 for each digit of accuracy". Args: - qb_op: qubit operator - digits (integer): number of digits of accuracy desired on expectation value + qb_op: qubit operator. + digits (integer): number of digits of accuracy desired on expectation + value. + Returns: - measurements (dict): Dictionary mapping terms / measurement bases to their number of measurements + dict: Dictionary mapping terms / measurement bases to their number of + measurements. """ available_methods = {'uniform'} diff --git a/qsdk/toolboxes/measurements/qubit_terms_grouping.py b/qsdk/toolboxes/measurements/qubit_terms_grouping.py index 71b843b41..7646a11dd 100644 --- a/qsdk/toolboxes/measurements/qubit_terms_grouping.py +++ b/qsdk/toolboxes/measurements/qubit_terms_grouping.py @@ -12,11 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" - This file provides access to different approaches aiming at reducing the number of measurements required - to compute the expectation value of a qubit (Pauli) operator. They attempt to identify the minimal amount of - measurement bases / terms required to deduce the expectation values associated to the other terms present in the - Pauli operator. +"""This file provides access to different approaches aiming at reducing the +number of measurements required to compute the expectation value of a qubit +(Pauli) operator. They attempt to identify the minimal amount of measurement +bases / terms required to deduce the expectation values associated to the other +terms present in the Pauli operator. """ import warnings @@ -25,39 +25,50 @@ def group_qwc(qb_ham, seed=None): - """ - Wrapper around Openfermion functionality that takes as input a QubitOperator and yields a collection of - mesurement bases defining a partition of groups of sub-operators with terms that are diagonal in the same tensor - product basis. Each sub-operator can be measured using the same qubit post-rotations in expectation estimation. - This uses the idea of qubitwise commutativity (qwc). + """Wrapper around Openfermion functionality that takes as input a + QubitOperator and yields a collection of mesurement bases defining a + partition of groups of sub-operators with terms that are diagonal in the + same tensor product basis. Each sub-operator can be measured using the same + qubit post-rotations in expectation estimation. This uses the idea of + qubitwise commutativity (qwc). + + The resulting dictionary maps the measurement basis (key) to the list of + qubit operators whose expectation value can be computed using the + corresponding circuit. The size of this dictionary determines how many + quantum circuits need to be executed in order to provide the expectation + value of the input qubit operator. - The resulting dictionary maps the measurement basis (key) to the list of qubit operators whose expectation - value can be computed using the corresponding circuit. The size of this dictionary determines how many - quantum circuits need to be executed in order to provide the expectation value of the input qubit operator. + Args: + operator (QubitOperator): the operator that will be split into + sub-operators (tensor product basis sets). + seed (int): default None. Random seed used to initialize the + numpy.RandomState pseudo-RNG. - Args: - operator (QubitOperator): the operator that will be split into sub-operators (tensor product basis sets). - seed (int): default None. Random seed used to initialize the numpy.RandomState pseudo-RNG. - Returns: - sub_operators (dict): a dictionary where each key defines a tensor product basis, - and each corresponding value is a QubitOperator with terms that are all diagonal in that basis. + Returns: + dict: a dictionary where each key defines a tensor product basis, and + each corresponding value is a QubitOperator with terms that are all + diagonal in that basis. """ return group_into_tensor_product_basis_sets(qb_ham, seed) def exp_value_from_measurement_bases(sub_ops, histograms): - """ - Computes the expectation value of the sum of all suboperators corresponding to the different measurement bases - in the input dictionary. This is how one would go about computing the expectation value of an operator - while trying to send a limited amount of tasks to a quantum computer, thereby lowering the cost in terms of - number of measurements needed. + """Computes the expectation value of the sum of all suboperators + corresponding to the different measurement bases in the input dictionary. + This is how one would go about computing the expectation value of an + operator while trying to send a limited amount of tasks to a quantum + computer, thereby lowering the cost in terms of number of measurements + needed. + + Args: + sub_ops (dict): Maps measurement bases to suboperators whose exp. value + can be computed from it. + histograms (dict): Maps measurement bases to histograms corresponding to + that quantum circuit. - Args: - sub_ops (dict): Maps measurement bases to suboperators whose exp. value can be computed from it - histograms (dict): Maps measurement bases to histograms corresponding to that quantum circuit - Returns: - exp_value (float or complex): Expectation value of the sum of all suboperators + Returns: + float or complex: Expectation value of the sum of all suboperators. """ # Warning if dicts dont have exact set of keys diff --git a/qsdk/toolboxes/molecular_computation/frozen_orbitals.py b/qsdk/toolboxes/molecular_computation/frozen_orbitals.py index 531d29e70..0e326366e 100644 --- a/qsdk/toolboxes/molecular_computation/frozen_orbitals.py +++ b/qsdk/toolboxes/molecular_computation/frozen_orbitals.py @@ -26,7 +26,7 @@ def get_frozen_core(molecule): molecule (pyscf.gto): Molecule to be evaluated. Returns: - frozen_core (int): First N molecular orbitals to freeze. + int: First N molecular orbitals to freeze. """ # Freezing core of each atom. "Row" refers to a periodic table row. @@ -59,8 +59,7 @@ def get_orbitals_excluding_homo_lumo(molecule, homo_minus_n=0, lumo_plus_n=0): lumo_plus_n (int): Ending point at LUMO + lumo_plus_n. Returns: - frozen_orbitals (list of int): Frozen orbitals not detected in the - active space. + list of int: Frozen orbitals not detected in the active space. """ # Getting the number of molecular orbitals. It also works with different diff --git a/qsdk/toolboxes/molecular_computation/molecule.py b/qsdk/toolboxes/molecular_computation/molecule.py index 46d36cc77..8b93e7569 100644 --- a/qsdk/toolboxes/molecular_computation/molecule.py +++ b/qsdk/toolboxes/molecular_computation/molecule.py @@ -125,13 +125,13 @@ def to_pyscf(self, basis="sto-3g"): return mol def to_openfermion(self, basis="sto-3g"): - """ Method to return a openfermion.MolecularData object. + """Method to return a openfermion.MolecularData object. - Args: - basis (string): Basis set. + Args: + basis (string): Basis set. - Returns: - openfermion.MolecularData: Openfermion compatible object. + Returns: + openfermion.MolecularData: Openfermion compatible object. """ return openfermion.MolecularData(self.xyz, basis, self.spin+1, self.q) @@ -259,7 +259,7 @@ def _compute_mean_field(self): run_ccsd=False, run_fci=False) - self.mf_energy =of_molecule.hf_energy + self.mf_energy = of_molecule.hf_energy self.mo_energies = of_molecule.orbital_energies self.mo_occ = of_molecule._pyscf_data["scf"].mo_occ diff --git a/qsdk/toolboxes/operators/operators.py b/qsdk/toolboxes/operators/operators.py index a14f77621..e7dd1413a 100644 --- a/qsdk/toolboxes/operators/operators.py +++ b/qsdk/toolboxes/operators/operators.py @@ -108,7 +108,7 @@ def normal_ordered(fe_op): reordering. Returns: - toolboxes.operators.FermionicOperator: Normal ordered operator. + FermionicOperator: Normal ordered operator. """ # Obtain normal ordered fermionic operator as list of terms @@ -126,7 +126,7 @@ def squared_normal_ordered(all_terms): or openfermion.FermionOperator Returns: - toolboxes.operators.FermionOperator: squared (i.e. fe_op*fe_op) and + FermionOperator: squared (i.e. fe_op*fe_op) and normal ordered. """ @@ -140,7 +140,7 @@ def list_to_fermionoperator(all_terms): """Input: a list of terms to generate FermionOperator Returns: - toolboxes.operators.FermionOperator: Single merged operator. + FermionOperator: Single merged operator. """ fe_op = FermionOperator() diff --git a/qsdk/toolboxes/post_processing/bootstrapping.py b/qsdk/toolboxes/post_processing/bootstrapping.py index 013bd53b6..20401a97e 100644 --- a/qsdk/toolboxes/post_processing/bootstrapping.py +++ b/qsdk/toolboxes/post_processing/bootstrapping.py @@ -27,8 +27,7 @@ def get_resampled_frequencies(freq_dict, ncount): ncount (int): number of shots/samples to generate resampled frequencies. Returns - frequencies (dict): new frequencies dictionary with resampled - distribution. + dict: new frequencies dictionary with resampled distribution. """ length_dict = len(freq_dict.keys()) diff --git a/qsdk/toolboxes/post_processing/mc_weeny_rdm_purification.py b/qsdk/toolboxes/post_processing/mc_weeny_rdm_purification.py index af9b1a5ae..9a4c3e3fd 100644 --- a/qsdk/toolboxes/post_processing/mc_weeny_rdm_purification.py +++ b/qsdk/toolboxes/post_processing/mc_weeny_rdm_purification.py @@ -30,8 +30,10 @@ def mcweeny_purify_2rdm(rdm2_spin, conv=1.0e-07): notation). conv (float), optional: The convergence criteria for McWeeny"s purification. + Returns: - One- and two-particle RDMs in spatial orbital basis (in chemistry + numpy.array: One-particle RDM in spatial orbital basis. + numpy.array: Two-particle RDM in spatial orbital basis (in chemistry notation). """ diff --git a/qsdk/toolboxes/qubit_mappings/bravyi_kitaev.py b/qsdk/toolboxes/qubit_mappings/bravyi_kitaev.py index f2d894525..0388925cc 100644 --- a/qsdk/toolboxes/qubit_mappings/bravyi_kitaev.py +++ b/qsdk/toolboxes/qubit_mappings/bravyi_kitaev.py @@ -42,8 +42,7 @@ def bravyi_kitaev(fermion_operator, n_qubits): n_qubits (int): number of qubits associated with the operator. Returns: - qubit_operator (QubitOperator): output bravyi-kitaev encoded qubit - operator. + QubitOperator: output bravyi-kitaev encoded qubit operator. """ if not (type(n_qubits) is int): raise TypeError("Number of qubits (n_qubits) must be integer type.") diff --git a/qsdk/toolboxes/qubit_mappings/jordan_wigner.py b/qsdk/toolboxes/qubit_mappings/jordan_wigner.py index 59e88e7c6..6b9ecea2d 100644 --- a/qsdk/toolboxes/qubit_mappings/jordan_wigner.py +++ b/qsdk/toolboxes/qubit_mappings/jordan_wigner.py @@ -48,7 +48,7 @@ def jordan_wigner(operator): a_j -> Z_0 .. Z_{j-1} (X_j + iY_j) / 2 Returns: - transformed_operator: An instance of the QubitOperator class. + QubitOperator: An instance of the QubitOperator class. Warning: The runtime of this method is exponential in the maximum locality of the @@ -150,7 +150,7 @@ def jordan_wigner_interaction_op(iop, n_qubits=None): This only works for real InteractionOperators (no complex numbers). Returns: - qubit_operator: An instance of the QubitOperator class. + QubitOperator: An instance of the QubitOperator class. """ if n_qubits is None: n_qubits = count_qubits(iop) diff --git a/qsdk/toolboxes/qubit_mappings/mapping_transform.py b/qsdk/toolboxes/qubit_mappings/mapping_transform.py index 24496056c..6a566246a 100644 --- a/qsdk/toolboxes/qubit_mappings/mapping_transform.py +++ b/qsdk/toolboxes/qubit_mappings/mapping_transform.py @@ -39,7 +39,7 @@ def get_qubit_number(mapping, n_spinorbitals): n_spinorbitals (int): number of spin-orbitals for molecule. Returns: - (int): number of qubits. + int: number of qubits. """ if mapping.upper() == "SCBK": return n_spinorbitals - 2 @@ -56,7 +56,7 @@ def get_fermion_operator(operator): operator (SymbolicOperator): input operator to be cast. Returns: - fermion_operator (FermionOperator). + FermionOperator: Self-explanatory. """ if not isinstance(operator, Iterable): raise TypeError("Input must be iterable suitable for casting to FermionOperator type.") @@ -91,8 +91,7 @@ def fermion_to_qubit_mapping(fermion_operator, mapping, n_spinorbitals=None, n_e then all spin down. Returns: - qubit_operator (QubitOperator): input operator, encoded in the qubit - space. + QubitOperator: input operator, encoded in the qubit space. """ # some methods may pass another operator class type. If this is the case, cast to FermionOperator where possible if not isinstance(fermion_operator, FermionOperator): @@ -135,8 +134,7 @@ def make_up_then_down(fermion_operator, n_spinorbitals): n_spinorbitals (int): number of spin-orbitals in register. Returns: - new_operator (FermionOperator): operator with all spin up followed by - all spin down. + FermionOperator: operator with all spin up followed by all spin down. """ if not isinstance(fermion_operator, FermionOperator): raise TypeError("Invalid operator input. Must be FermionOperator.") diff --git a/qsdk/toolboxes/qubit_mappings/statevector_mapping.py b/qsdk/toolboxes/qubit_mappings/statevector_mapping.py index 7aaba954f..b4ee73944 100644 --- a/qsdk/toolboxes/qubit_mappings/statevector_mapping.py +++ b/qsdk/toolboxes/qubit_mappings/statevector_mapping.py @@ -43,8 +43,8 @@ def get_vector(n_spinorbitals, n_electrons, mapping, up_then_down=False, spin=No alternating spin up/down. Returns: - vector (numpy array of int): binary integer array indicating occupation - of each spin-orbital. + numpy array of int: binary integer array indicating occupation of each + spin-orbital. """ if mapping.upper() not in available_mappings: raise ValueError(f"Invalid mapping selection. Select from: {available_mappings}") @@ -79,7 +79,7 @@ def do_bk_transform(vector): vector (numpy array of int): fermion occupation vector. Returns: - vector_bk (numpy array of int): qubit-encoded occupation vector. + numpy array of int: qubit-encoded occupation vector. """ mat = bravyi_kitaev_code(len(vector)).encoder.toarray() vector_bk = np.mod(np.dot(mat, vector), 2) @@ -92,10 +92,10 @@ def do_scbk_transform(n_spinorbitals, n_electrons): Args: n_spinorbitals (int): number of qubits in register. - n_electrons (int): number of fermions occupied + n_electrons (int): number of fermions occupied. Returns: - vector (numpy array of int): qubit-encoded occupation vector. + numpy array of int: qubit-encoded occupation vector. """ n_alpha, n_orb = n_electrons//2, (n_spinorbitals - 2)//2 vector = np.zeros(n_spinorbitals - 2, dtype=int) @@ -113,7 +113,7 @@ def vector_to_circuit(vector): vector (numpy array of int): occupation vector. Returns: - circuit (Circuit): instance of qsdk.backendbuddy Circuit class + Circuit: instance of qsdk.backendbuddy Circuit class. """ n_qubits = len(vector) @@ -142,7 +142,7 @@ def get_reference_circuit(n_spinorbitals, n_electrons, mapping, up_then_down=Fal spin (int): 2*S = n_alpha - n_beta. Returns: - circuit (Circuit): instance of qsdk.backendbuddy Circuit class + Circuit: instance of qsdk.backendbuddy Circuit class. """ vector = get_vector(n_spinorbitals, n_electrons, mapping, up_then_down=up_then_down, spin=spin) circuit = vector_to_circuit(vector) diff --git a/qsdk/toolboxes/qubit_mappings/symmetry_conserving_bravyi_kitaev.py b/qsdk/toolboxes/qubit_mappings/symmetry_conserving_bravyi_kitaev.py index 6e878487d..3dcad91ab 100644 --- a/qsdk/toolboxes/qubit_mappings/symmetry_conserving_bravyi_kitaev.py +++ b/qsdk/toolboxes/qubit_mappings/symmetry_conserving_bravyi_kitaev.py @@ -60,9 +60,8 @@ def symmetry_conserving_bravyi_kitaev(fermion_operator, n_spinorbitals, ordered putting all spin up before all spin down states. Returns: - qubit_operator (QubitOperator): The qubit operator corresponding to the - supplied fermionic operator, with two qubits removed using spin - symmetries. + QubitOperator: The qubit operator corresponding to the supplied + fermionic operator, with two qubits removed using spin symmetries. WARNING: Reorders orbitals from the default even-odd ordering to all spin-up @@ -142,8 +141,8 @@ def edit_operator_for_spin(qubit_operator, spin_orbital, orbital_parity): orbital_parity (int): plus/minus one, parity of eigenvalue. Returns: - qubit_operator (QubitOperator): updated operator, with relevant - coefficients multiplied by +/-1. + QubitOperator: updated operator, with relevant coefficients multiplied + by +/-1. """ new_qubit_dict = {} for term, coefficient in qubit_operator.terms.items(): @@ -190,8 +189,7 @@ def prune_unused_indices(qubit_operator, prune_indices, n_qubits): n_qubits (int): number of qubits in register. Returns: - new_operator (QubitOperator): output operator, with designated qubit - indices excised. + QubitOperator: output operator, with designated qubit indices excised. """ indices = np.linspace(0, n_qubits - 1, n_qubits, dtype=int) From cdb78d707fde00c44cb122836a51745e840aa914 Mon Sep 17 00:00:00 2001 From: AlexandreF-1qbit <76115575+AlexandreF-1qbit@users.noreply.github.com> Date: Tue, 2 Nov 2021 17:06:16 -0400 Subject: [PATCH 05/68] Suppression of misc warnings (#80) * No more random SCF energy = ... * Forcing real chemical potential in DMET. * Hidding prints with scipy opt in VQE. * Silence scipy outputs. * ADAPT like VQE outputs. * Pyscf import warning. Co-authored-by: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Co-authored-by: James Brown --- qsdk/__init__.py | 9 +++++++++ qsdk/algorithms/variational/adapt_vqe_solver.py | 16 ++++++++++------ qsdk/algorithms/variational/vqe_solver.py | 16 +++++++++++----- qsdk/helpers/utils.py | 16 +++++++++++++++- .../dmet/dmet_problem_decomposition.py | 1 + qsdk/toolboxes/ansatz_generator/uccsd.py | 2 +- qsdk/toolboxes/molecular_computation/molecule.py | 11 +++++------ 7 files changed, 52 insertions(+), 19 deletions(-) diff --git a/qsdk/__init__.py b/qsdk/__init__.py index e278e9a8e..3ceae4420 100644 --- a/qsdk/__init__.py +++ b/qsdk/__init__.py @@ -12,4 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +import warnings +import numpy as np + +sup = np.testing.suppress_warnings() +warnings.filterwarnings("ignore", message="Using default_file_mode other than 'r' is deprecated") +warnings.filterwarnings("ignore", message="`np") +warnings.filterwarnings("ignore", category=DeprecationWarning) +sup.filter(np.core) + from qsdk.toolboxes.molecular_computation.molecule import Molecule, SecondQuantizedMolecule diff --git a/qsdk/algorithms/variational/adapt_vqe_solver.py b/qsdk/algorithms/variational/adapt_vqe_solver.py index 1c109051e..174fbf127 100644 --- a/qsdk/algorithms/variational/adapt_vqe_solver.py +++ b/qsdk/algorithms/variational/adapt_vqe_solver.py @@ -80,7 +80,7 @@ def __init__(self, opt_dict): "n_electrons": None, "optimizer": self.LBFGSB_optimizer, "backend_options": default_backend_options, - "verbose": True} + "verbose": False} # Initialize with default values self.__dict__ = default_options @@ -207,7 +207,8 @@ def simulate(self): # all operator gradients are less than self.tol. while self.iteration < self.max_cycles: self.iteration += 1 - print(f"Iteration {self.iteration} of ADAPT-VQE.") + if self.verbose: + print(f"Iteration {self.iteration} of ADAPT-VQE.") pool_select = self.rank_pool(self.pool_commutators, self.vqe_solver.ansatz.circuit, backend=self.vqe_solver.backend, tolerance=self.tol) @@ -274,7 +275,7 @@ def LBFGSB_optimizer(self, func, var_params): """Default optimizer for ADAPT-VQE.""" result = minimize(func, var_params, method="L-BFGS-B", - options={"disp": False, "maxiter": 100, "gtol": 1e-10, "iprint": -1}) + options={"disp": False, "maxiter": 100, "gtol": 1e-10, "iprint": -1}) self.optimal_var_params = result.x self.optimal_energy = result.fun @@ -286,8 +287,11 @@ def LBFGSB_optimizer(self, func, var_params): self.optimal_circuit = self.vqe_solver.ansatz.circuit if self.verbose: - print(f"\t\tOptimal VQE energy: {self.optimal_energy}") - print(f"\t\tOptimal VQE variational parameters: {self.optimal_var_params}") - print(f"\t\tNumber of Function Evaluations : {result.nfev}") + print(f"VQESolver optimization results:") + print(f"\tOptimal VQE energy: {result.fun}") + print(f"\tOptimal VQE variational parameters: {result.x}") + print(f"\tNumber of Iterations : {result.nit}") + print(f"\tNumber of Function Evaluations : {result.nfev}") + print(f"\tNumber of Gradient Evaluations : {result.njev}") return result.fun, result.x diff --git a/qsdk/algorithms/variational/vqe_solver.py b/qsdk/algorithms/variational/vqe_solver.py index 870fb62b0..a36c0bfba 100644 --- a/qsdk/algorithms/variational/vqe_solver.py +++ b/qsdk/algorithms/variational/vqe_solver.py @@ -23,6 +23,7 @@ import numpy as np from openfermion.ops.operators.qubit_operator import QubitOperator +from qsdk.helpers.utils import HiddenPrints from qsdk.backendbuddy import Simulator, Circuit from qsdk.backendbuddy.helpers.circuits.measurement_basis import measurement_basis_gates from qsdk.toolboxes.operators import count_qubits, FermionOperator, qubitop_to_qubitham @@ -451,12 +452,17 @@ def _default_optimizer(self, func, var_params): """ from scipy.optimize import minimize - result = minimize(func, var_params, method="SLSQP", - options={"disp": True, "maxiter": 2000, "eps": 1e-5, "ftol": 1e-5}) + + with HiddenPrints(): + result = minimize(func, var_params, method="SLSQP", + options={"disp": True, "maxiter": 2000, "eps": 1e-5, "ftol": 1e-5}) if self.verbose: - print(f"\t\tOptimal VQE energy: {self.optimal_energy}") - print(f"\t\tOptimal VQE variational parameters: {self.optimal_var_params}") - print(f"\t\tNumber of Function Evaluations : {result.nfev}") + print(f"VQESolver optimization results:") + print(f"\tOptimal VQE energy: {result.fun}") + print(f"\tOptimal VQE variational parameters: {result.x}") + print(f"\tNumber of Iterations : {result.nit}") + print(f"\tNumber of Function Evaluations : {result.nfev}") + print(f"\tNumber of Gradient Evaluations : {result.njev}") return result.fun, result.x diff --git a/qsdk/helpers/utils.py b/qsdk/helpers/utils.py index 79081c5ad..789b65046 100644 --- a/qsdk/helpers/utils.py +++ b/qsdk/helpers/utils.py @@ -17,10 +17,24 @@ a default simulator. """ +import os, sys + + +class HiddenPrints: + """Class to hide terminal printing with a 'with' block.""" + + def __enter__(self): + self._original_stdout = sys.stdout + sys.stdout = open(os.devnull, "w") + + def __exit__(self, exc_type, exc_val, exc_tb): + sys.stdout.close() + sys.stdout = self._original_stdout + def is_package_installed(package_name): try: - exec(f'import {package_name}') + exec(f"import {package_name}") # DEBUG print(f'{package_name:16s} :: found') return True except ModuleNotFoundError: diff --git a/qsdk/problem_decomposition/dmet/dmet_problem_decomposition.py b/qsdk/problem_decomposition/dmet/dmet_problem_decomposition.py index 73737176b..bee366160 100644 --- a/qsdk/problem_decomposition/dmet/dmet_problem_decomposition.py +++ b/qsdk/problem_decomposition/dmet/dmet_problem_decomposition.py @@ -237,6 +237,7 @@ def simulate(self): raise RuntimeError("No fragment built. Have you called DMET.build ?") self.chemical_potential = self.optimizer(self._oneshot_loop, self.initial_chemical_potential) + self.chemical_potential = self.chemical_potential.real # run one more time to save results _ = self._oneshot_loop(self.chemical_potential, save_results=True) diff --git a/qsdk/toolboxes/ansatz_generator/uccsd.py b/qsdk/toolboxes/ansatz_generator/uccsd.py index 17cb7886f..b29341e15 100644 --- a/qsdk/toolboxes/ansatz_generator/uccsd.py +++ b/qsdk/toolboxes/ansatz_generator/uccsd.py @@ -251,7 +251,7 @@ def _compute_mp2_params(self): list of float: The initial variational parameters. """ - mp2_fragment = mp.MP2(self.molecule.to_pyscf(self.molecule.basis), frozen=self.molecule.frozen_mos) + mp2_fragment = mp.MP2(self.molecule.mean_field, frozen=self.molecule.frozen_mos) mp2_fragment.verbose = 0 _, mp2_t2 = mp2_fragment.kernel() diff --git a/qsdk/toolboxes/molecular_computation/molecule.py b/qsdk/toolboxes/molecular_computation/molecule.py index 8b93e7569..6fc2cc667 100644 --- a/qsdk/toolboxes/molecular_computation/molecule.py +++ b/qsdk/toolboxes/molecular_computation/molecule.py @@ -22,6 +22,7 @@ from pyscf import gto, scf, ao2mo import openfermion import openfermionpyscf +from openfermionpyscf import run_pyscf from openfermion.ops.representations.interaction_operator import get_active_space_integrals from qsdk.toolboxes.molecular_computation.frozen_orbitals import get_frozen_core @@ -253,11 +254,8 @@ def _compute_mean_field(self): (mf_energy, mo_energies, mo_occ, n_mos and n_sos). """ of_molecule = self.to_openfermion(self.basis) - of_molecule = openfermionpyscf.run_pyscf(of_molecule, run_scf=True, - run_mp2=False, - run_cisd=False, - run_ccsd=False, - run_fci=False) + of_molecule = run_pyscf(of_molecule, run_scf=True, run_mp2=False, + run_cisd=False, run_ccsd=False, run_fci=False) self.mf_energy = of_molecule.hf_energy self.mo_energies = of_molecule.orbital_energies @@ -281,7 +279,8 @@ def _get_fermionic_hamiltonian(self): active_indices = self.active_mos of_molecule = self.to_openfermion(self.basis) - of_molecule = openfermionpyscf.run_pyscf(of_molecule, run_scf=True, run_mp2=False, run_cisd=False, run_ccsd=False, run_fci=False) + of_molecule = run_pyscf(of_molecule, run_scf=True, run_mp2=False, + run_cisd=False, run_ccsd=False, run_fci=False) molecular_hamiltonian = of_molecule.get_molecular_hamiltonian(occupied_indices, active_indices) From e9186d9883768dc97cb20d429b92021d586e2861 Mon Sep 17 00:00:00 2001 From: JamesB-1qbit <84878946+JamesB-1qbit@users.noreply.github.com> Date: Fri, 5 Nov 2021 14:35:46 -0400 Subject: [PATCH 06/68] small fixes to allow initial density matrix for faster noisy sampling with cirq (#84) --- qsdk/backendbuddy/simulator.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/qsdk/backendbuddy/simulator.py b/qsdk/backendbuddy/simulator.py index 0b6639cf6..a0338076d 100644 --- a/qsdk/backendbuddy/simulator.py +++ b/qsdk/backendbuddy/simulator.py @@ -117,8 +117,9 @@ def simulate(self, source_circuit, return_statevector=False, initial_statevector if source_circuit.width == 0: raise ValueError("Cannot simulate an empty circuit (e.g identity unitary) with unknown number of qubits.") - # If the unitary is the identity (no gates), no need for simulation: return all-zero state - if source_circuit.size == 0: + # If the unitary is the identity (no gates) and no noise model, no need for simulation: + # return all-zero state or sample from statevector + if source_circuit.size == 0 and not self._noise_model: if initial_statevector is not None: statevector = initial_statevector frequencies = self._statevector_to_frequencies(initial_statevector) @@ -409,7 +410,7 @@ def _get_expectation_value_from_frequencies(self, qubit_operator, state_prep_cir n_qubits = state_prep_circuit.width if not self.statevector_available or state_prep_circuit.is_mixed_state or self._noise_model: initial_circuit = state_prep_circuit - if initial_statevector is not None: + if initial_statevector is not None and not self.statevector_available: raise ValueError(f'Backend {self._target} does not support statevectors') else: updated_statevector = initial_statevector From fdfff159c53d8f4474dbf0c0e65e2d2bf2b269b3 Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Fri, 5 Nov 2021 13:47:33 -0700 Subject: [PATCH 07/68] Update sphinx docs nov2021 (#85) * Adding new notebook and rewiring docs * Modified files to facilitate re-generating docs Co-authored-by: AlexandreF-1qbit <76115575+AlexandreF-1qbit@users.noreply.github.com> --- dev_tools/build_sphinx_docs.sh | 9 + dev_tools/requirements.txt | 189 +++ docs/source/conf.py | 2 + docs/source/index.rst | 2 +- docs/source/overview_endtoend.ipynb | 1 + ...st_cloud_hardware_experiments_braket.ipynb | 1 + docs/source/tutorials.rst | 4 + docs/source/vqe.ipynb | 1 + examples/backendbuddy/2.qpu_connection.ipynb | 572 -------- examples/img/DMET_H10.png | Bin 0 -> 40521 bytes examples/img/DMET_H10_fragment.png | Bin 0 -> 63391 bytes examples/img/DMET_published_H10_results.png | Bin 0 -> 94503 bytes examples/overview_endtoend.ipynb | 1279 +++++++++++++++++ examples/problem_decomposition_oniom.ipynb | 4 +- 14 files changed, 1489 insertions(+), 575 deletions(-) create mode 100644 dev_tools/requirements.txt create mode 120000 docs/source/overview_endtoend.ipynb create mode 120000 docs/source/qemist_cloud_hardware_experiments_braket.ipynb create mode 120000 docs/source/vqe.ipynb delete mode 100755 examples/backendbuddy/2.qpu_connection.ipynb create mode 100644 examples/img/DMET_H10.png create mode 100644 examples/img/DMET_H10_fragment.png create mode 100644 examples/img/DMET_published_H10_results.png create mode 100644 examples/overview_endtoend.ipynb diff --git a/dev_tools/build_sphinx_docs.sh b/dev_tools/build_sphinx_docs.sh index 145a0a486..41a99d6de 100644 --- a/dev_tools/build_sphinx_docs.sh +++ b/dev_tools/build_sphinx_docs.sh @@ -4,5 +4,14 @@ # or mock the desired packages in docs/source/conf.py cd ../docs || cd docs pip install sphinx sphinx_rtd_theme nbsphinx + +# This environment seems to work for building the docs. +# Something fishy otherwise. +pip install -r requirements.txt + +# Support for LaTeX sudo apt-get install -y latexmk texlive-latex-recommended texlive-latex-extra texlive-fonts-recommended pandoc dvipng + +# Build html documentation in ../docs/source/html +sphinx-apidoc -o source ../qsdk make clean; make html diff --git a/dev_tools/requirements.txt b/dev_tools/requirements.txt new file mode 100644 index 000000000..ed010efb7 --- /dev/null +++ b/dev_tools/requirements.txt @@ -0,0 +1,189 @@ +alabaster==0.7.12 +amazon-braket-default-simulator==1.1.1.post2 +amazon-braket-schemas==1.1.3 +amazon-braket-sdk==1.5.15 +argon2-cffi==21.1.0 +attrs==20.3.0 +Babel==2.9.1 +backcall==0.2.0 +backoff==1.10.0 +beautifulsoup4==4.10.0 +bitarray==2.0.1 +bleach==4.1.0 +boltons==20.2.1 +boto3==1.17.66 +botocore==1.20.66 +cachetools==4.2.2 +certifi==2020.12.5 +cffi==1.14.5 +chardet==4.0.0 +charset-normalizer==2.0.7 +cirq==0.10.0 +cirq-aqt==0.13.1 +cirq-core==0.12.0 +cirq-google==0.12.0 +cirq-ionq==0.13.1 +cirq-pasqal==0.13.1 +cirq-rigetti==0.13.1 +cirq-web==0.13.1 +coverage==5.5 +cryptography==3.4.7 +cycler==0.10.0 +debugpy==1.5.1 +decorator==4.4.2 +defusedxml==0.7.1 +deprecation==2.1.0 +dill==0.3.3 +dlx==1.0.4 +docplex==2.20.204 +docutils==0.17.1 +duet==0.2.3 +entrypoints==0.3 +fastdtw==0.3.4 +fastjsonschema==2.15.0 +fonttools==4.27.1 +future==0.18.2 +geometric==0.9.7.2 +google-api-core==1.26.3 +google-auth==1.30.0 +google-cloud==0.34.0 +googleapis-common-protos==1.53.0 +graphviz==0.16 +grpcio==1.37.1 +h11==0.9.0 +h5py==2.9.0 +httpcore==0.11.1 +httpx==0.15.5 +idna==2.10 +imagesize==1.2.0 +inflection==0.5.1 +iniconfig==1.1.1 +ipykernel==5.5.5 +ipython==7.23.1 +ipython-genutils==0.2.0 +ipywidgets==7.6.5 +iso8601==0.1.16 +jedi==0.18.0 +Jinja2==2.11.3 +jmespath==0.10.0 +joblib==1.0.1 +jsonschema==3.2.0 +jupyter==1.0.0 +jupyter-client==6.1.12 +jupyter-console==6.4.0 +jupyter-core==4.7.1 +jupyterlab-pygments==0.1.2 +jupyterlab-widgets==1.0.2 +kiwisolver==1.3.1 +lark==0.11.3 +lark-parser==0.11.3 +lxml==4.6.3 +MarkupSafe==2.0.1 +matplotlib==3.4.1 +matplotlib-inline==0.1.2 +mistune==0.8.4 +more-itertools==8.8.0 +mpmath==1.2.1 +msgpack==0.6.2 +multitasking==0.0.9 +nbclient==0.5.4 +nbconvert==6.2.0 +nbformat==5.1.3 +nbsphinx==0.8.7 +nest-asyncio==1.5.1 +networkx==2.5.1 +notebook==6.4.5 +ntlm-auth==1.5.0 +numpy==1.20.1 +openfermion==1.0.1 +openfermionpyscf==0.5 +opt-einsum==3.3.0 +packaging==20.9 +pandas==1.2.3 +pandocfilters==1.5.0 +parso==0.8.2 +pexpect==4.8.0 +pickleshare==0.7.5 +Pillow==8.2.0 +pluggy==1.0.0.dev0 +ply==3.11 +prometheus-client==0.11.0 +prompt-toolkit==3.0.18 +protobuf==3.13.0 +psutil==5.8.0 +ptyprocess==0.7.0 +PubChemPy==1.0.4 +py==1.10.0 +py3Dmol==0.9.1 +pyasn1==0.4.8 +pyasn1-modules==0.2.8 +pybind11==2.6.2 +pycparser==2.20 +pydantic==1.8.1 +Pygments==2.9.0 +PyJWT==1.7.1 +pylatexenc==2.10 +pyparsing==3.0.0b2 +pyquil==3.0.1 +pyrsistent==0.17.3 +pyscf==1.7.6 +pytest==6.2.4 +python-constraint==1.4.0 +python-dateutil==2.8.1 +python-rapidjson==1.5 +pytket==0.11.0 +pytket-qiskit==0.13.0 +pytz==2021.1 +pyzmq==19.0.2 +qcs-api-client==0.8.0 +qemist-client===0.2.1-cb867ac6 +qtconsole==5.1.1 +QtPy==1.11.2 +Quandl==3.6.0 +Qulacs==0.3.0 +requests==2.25.1 +requests-ntlm==1.1.0 +retry==0.9.2 +retrying==1.3.3 +retworkx==0.8.0 +rfc3339==6.2 +rfc3986==1.5.0 +rpcq==3.9.2 +rsa==4.7.2 +ruamel.yaml==0.17.17 +ruamel.yaml.clib==0.2.6 +s3transfer==0.4.2 +scikit-learn==0.24.1 +scipy==1.6.1 +Send2Trash==1.8.0 +setuptools-scm==6.3.2 +six==1.15.0 +sniffio==1.2.0 +snowballstemmer==2.1.0 +sortedcontainers==2.3.0 +soupsieve==2.2.1 +Sphinx==4.2.0 +sphinx-rtd-theme==1.0.0 +sphinxcontrib-applehelp==1.0.2 +sphinxcontrib-devhelp==1.0.2 +sphinxcontrib-htmlhelp==2.0.0 +sphinxcontrib-jsmath==1.0.1 +sphinxcontrib-qthelp==1.0.3 +sphinxcontrib-serializinghtml==1.1.5 +sseclient==0.0.27 +sympy==1.7.1 +terminado==0.12.1 +testpath==0.5.0 +threadpoolctl==2.1.0 +toml==0.10.2 +tomli==1.2.1 +tornado==6.1 +tqdm==4.60.0 +traitlets==5.0.5 +typing-extensions==3.10.0.0 +urllib3==1.26.4 +wcwidth==0.2.5 +webencodings==0.5.1 +websockets==9.0.1 +widgetsnbextension==3.5.1 +yfinance==0.1.55 diff --git a/docs/source/conf.py b/docs/source/conf.py index 796bb059e..cf0165777 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -48,6 +48,8 @@ # html_theme = 'sphinx_rtd_theme' +napoleon_custom_sections = [('Returns', 'params_style')] + # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". diff --git a/docs/source/index.rst b/docs/source/index.rst index bdc32c2a7..c08e4792b 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -11,8 +11,8 @@ Welcome to qsdk's documentation! :caption: Contents: README - qsdk tutorials + qsdk Indices and tables ================== diff --git a/docs/source/overview_endtoend.ipynb b/docs/source/overview_endtoend.ipynb new file mode 120000 index 000000000..790869130 --- /dev/null +++ b/docs/source/overview_endtoend.ipynb @@ -0,0 +1 @@ +../../examples/overview_endtoend.ipynb \ No newline at end of file diff --git a/docs/source/qemist_cloud_hardware_experiments_braket.ipynb b/docs/source/qemist_cloud_hardware_experiments_braket.ipynb new file mode 120000 index 000000000..bfc83020b --- /dev/null +++ b/docs/source/qemist_cloud_hardware_experiments_braket.ipynb @@ -0,0 +1 @@ +../../examples/qemist_cloud_hardware_experiments_braket.ipynb \ No newline at end of file diff --git a/docs/source/tutorials.rst b/docs/source/tutorials.rst index 64333e5bd..e90011347 100644 --- a/docs/source/tutorials.rst +++ b/docs/source/tutorials.rst @@ -4,5 +4,9 @@ Tutorials .. toctree:: :maxdepth: 2 + overview_endtoend backendbuddy_basics + vqe dmet + problem_decomposition_oniom + qemist_cloud_hardware_experiments_braket diff --git a/docs/source/vqe.ipynb b/docs/source/vqe.ipynb new file mode 120000 index 000000000..f3f3441cd --- /dev/null +++ b/docs/source/vqe.ipynb @@ -0,0 +1 @@ +../../examples/vqe.ipynb \ No newline at end of file diff --git a/examples/backendbuddy/2.qpu_connection.ipynb b/examples/backendbuddy/2.qpu_connection.ipynb deleted file mode 100755 index 76c857d1c..000000000 --- a/examples/backendbuddy/2.qpu_connection.ipynb +++ /dev/null @@ -1,572 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Agnostic Simulator: QPU connection" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This notebook elaborates on how `qsdk.backendbuddy` can facilitate hardware experiments by directly integrating the API provided by some hardware providers (Honeywell, IonQ...) or broader quantum cloud services such as Azure Quantum or Braket.\n", - "\n", - "\n", - "## Table of contents\n", - "* [1. IonQ Rest API](#1)\n", - " * [1.1 References](#1.1)\n", - " * [1.2 Environment](#1.2)\n", - " * [1.3 Quantum Circuit](#1.3)\n", - " * [1.4 IonQConnection class](#1.4)\n", - "* [2. Honeywell Rest AI](#2)\n", - " * [2.1 References](#2.1)\n", - " * [2.2 Environment](#2.2)\n", - " * [2.3 Quantum Circuit](#2.3)\n", - " * [2.4 HoneywellConnection class](#2.4)\n", - "* [3. Azure Quantum](#3)\n", - "* [4. Braket](#4)\n", - "\n", - "---\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Requirements\n", - "\n", - "In order to run the contents of this notebook, I recommend that you first install the backendbuddy package as per the instructions, relying on the `setup.py` file in the github repository.\n", - "\n", - "Ensure you have valid ID tokens and logins for the services of your choice (IonQ, Honeywell, ...). This may require 1QBit to request them from the hardware providers.\n", - "\n", - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 1. IonQ Rest API \n", - "\n", - "\n", - "### 1.1 References \n", - "\n", - "We merely provide a few Python wrappers around the REST API provided by IonQ, to facilitate job submission and monitoring. The most up-to-date list of supported features and information regarding the IonQ API is available below (some special privileges may be required to access this material).\n", - "\n", - "https://dewdrop.ionq.co/\n", - "\n", - "https://docs.ionq.co\n", - "\n", - "### 1.2 Environment \n", - "\n", - "Users allowed to use the IonQ REST services should have been provided with an ID token (a rather long string of alphanumeric characters and dashes). For the REST requests to be succesful, users are expected to set their `IONQ_APIKEY` environment variable to this value.\n", - "\n", - "This can be done in your bash terminal (`export IONQ_APIKEY=`), or in your Python script (`os.environ['IONQ_APIKEY'] = `). Please make sure you do not upload or share code showing sensitive information, such as logins / tokens. If you export these variables before launching the Jupyter notebook server, they will be visible to the notebook.\n", - "\n", - "### 1.3 Quantum circuit \n", - "\n", - "IonQ relies on a general JSON representation for quantum circuits, which `qsdk.backendbuddy` can generate from an abstract circuit, using the `translator` module. IonQ also offer partial support for other common formats (OpenQASM, ...). Formats and quantum operations supported are available in their documentation. We chose to rely on the best-supported format, their JSON format, which takes the shape of nested list and dictionaries.\n", - "\n", - "Below, we show how users can produce a simple quantum circuit in this format:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from qsdk.backendbuddy.translator import Circuit, Gate, translate_json_ionq\n", - "\n", - "circ1 = Circuit([Gate(\"H\", 0), Gate(\"X\", 1)])\n", - "json_circ1 = translate_json_ionq(circ1)\n", - "print(json_circ1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 1.4 IonQConnection class \n", - "\n", - "The `IonQConnection` class encapsulates a collection of wrappers to the IonQ REST API. Internally, it stores information about the endpoint and the authorization header, containing your identification token. This class only is instantiated succesfully if your ID token has been set properly.\n", - "\n", - "More generally speaking, all calls to the REST API are checked for errors, and would return the IonQ error message corresponding to the unsuccesful request." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from qsdk.backendbuddy.qpu_connection import IonQConnection\n", - "\n", - "# import os\n", - "# os.environ[\"IONQ_APIKEY\"] = \n", - "\n", - "ionq_connection = IonQConnection()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### job submission" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Job submission can be achieved through the `job_submit` method, which attempts to submit a job on a simulator or QPU, and returns a job ID if submission was succesful.\n", - "\n", - "This method takes input arguments that need to be provided by the user:\n", - "\n", - "- the target backend (currently, IonQ provides two options: 'simulator' and 'qpu')\n", - "- the quantum circuit (assumed to be in JSON format, by default)\n", - "- the number of shots required (ignored by the simulator)\n", - "- a name for your job\n", - "- any other option as key arguments (see source code and IonQ documentation)\n", - "\n", - "Assuming a valid API key, and no issue with the IonQ services themselves, we can then submit a simple job targeting their statevector simulator. The status of the job may be in various states (queued, ready, running ...) depending on the status of the IonQ services themselves." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "job_id = ionq_connection.job_submit('simulator', json_circ1, 100, 'test_simulator_json_job')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### job history and job info\n", - "\n", - "As of July 2020, IonQ does not provide an online portal or dashboard allowing users to monitor their jobs. Users can however access their job history and info through the `job_get_history` and `job_get_info` methods, shown as below.\n", - "\n", - "Depending on the timing of your REST requests, the job info may differ widely, from a failed job to a completed job with results included." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "job_info = ionq_connection.job_get_info(job_id)\n", - "print(job_info)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The output of `job_get_history` should at least feature the job we just submitted, and can also show up to the last 25 submitted jobs that were not deleted from IonQ's database. The output is a `pandas` dataframe, in order to facilitate parsing of information." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "job_history = ionq_connection.job_get_history()\n", - "print(job_history)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### job results\n", - "\n", - "The method `job_get_results` provides a wrapper to a blocking call, querying for the state of the target job at regular intervals, attempting to retrieve the results. \n", - "\n", - "If the job has successfully completed, the 'data' entry of the json return dictionary is available to the user. Running a job using the `simulator` backend yields a histogram of probabilities. When running on a QPU, the data may be different, a may take the form of a list of all the shots, or a histogram agglomerating the counts into bins.\n", - "\n", - "IonQ raw results use a \"most-significant qubit first\" representation, encoded as an integer. It means that if the integer `i` is our result dictionary, the binary representation of `i`, which is a string of zeros and ones, correspond to $q_0 q_1...q_n$ (e.g measured qubit basis states are to be read left-to-right). This convention usually differs from the one used in theoretical literature, which usually reads right-to-left.\n", - "\n", - "For example,for a 2-qubit circuit:\n", - "\n", - "- 0 is '00' in binary, indicating $q_0$ mesured in state $|0\\rangle$, and $q_1$ in state $|0\\rangle$\n", - "- 1 is '01' in binary, indicating $q_0$ mesured in state $|1\\rangle$, and $q_1$ in state $|0\\rangle$\n", - "- 2 is '10' in binary, indicating $q_0$ mesured in state $|0\\rangle$, and $q_1$ in state $|1\\rangle$\n", - "- 3 is '11' in binary, indicating $q_0$ mesured in state $|1\\rangle$, and $q_1$ in state $|1\\rangle$\n", - "\n", - "In the future, `qsdk.backendbuddy` may return these in its own format, following a given convention." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "results = ionq_connection.job_get_results(job_id)\n", - "print(results)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### job cancel / delete\n", - "\n", - "A wrapper called `job_cancel` provides a method to cancel before execution (if the job hasn't run yet), or delete a job from the history. The cell below cancel / delete the previously submitted job: it therefore should not appear in the history anymore." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ionq_connection.job_cancel(job_id)\n", - "job_history = ionq_connection.job_get_history()\n", - "print(f\"\\n{job_history}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 2. Honeywell Rest API\n", - "\n", - "\n", - "### 2.1 References \n", - "\n", - "We merely provide a few Python wrappers around the REST API provided by Honeywell, to facilitate job submission and monitoring. The most up-to-date list of supported features, error codes and information regarding their API is available below (some special privileges may be required to access this material).\n", - "\n", - "https://drive.google.com/file/d/1PDQwIyAVlMn5JEMVWBKvEPkcawzh8EeG/view?usp=sharing\n", - "\n", - "Honeywell provides a portal / dashboard to facilitate job monitoring. Your 1QBit admin (currently Valentin Senicourt) can create accounts for new users:\n", - "\n", - "https://um.qapi.honeywell.com/index.html\n", - "\n", - "As of July 2020, this dashboard is fairly simple and looks like this:\n", - "\n", - "![Honeywell dashboard](img/honeywell_dashboard.png)\n", - "\n", - "\n", - "### 2.2 Environment \n", - "\n", - "For the REST requests to be succesful, users are expected to set their `HONEYWELL_EMAIL` and `HONEYWELL_PASSWORD` environment variables to the correct values, which are the logins used for the online portal above.\n", - "\n", - "This can be done in your bash terminal (`export HONEYWELL_EMAIL=`), or in your Python script (`os.environ['HONEYWELL_EMAIL'] = `). Please make sure you do not upload or share code showing sensitive information, such as logins / tokens. If you export these variables before launching the Jupyter notebook server, they will be visible to the notebook.\n", - "\n", - "### 2.3 Quantum circuit \n", - "\n", - "Honeywell supports the OpenQASM 2.0 format. Users can use the `translate_openqasm` function from the `translator` module in `qsdk.backendbuddy` to obtain their circuit in an acceptable format. Otherwise, many packages (Qiskit, etc) can also easily export quantum circuits in this format as well, in case `qsdk.backendbuddy` does not support all the relevant instructions." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from qsdk.backendbuddy.translator import Circuit, Gate, translate_openqasm\n", - "\n", - "circ1 = Circuit([Gate(\"H\", 0), Gate(\"X\", 1)])\n", - "qasm_circ1 = translate_openqasm(circ1)\n", - "print(qasm_circ1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 2.4 HoneywellConnection class \n", - "\n", - "The `HoneywellConnection` class encapsulates a collection of wrappers to their REST API. Internally, it stores information about the endpoint and the authorization header, containing your identification token. This class only is instantiated succesfully if your login information has been set properly.\n", - "\n", - "More generally speaking, all calls to the REST API are checked for errors, and would return the REST error message corresponding to the unsuccesful request." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from qsdk.backendbuddy.qpu_connection import HoneywellConnection\n", - "\n", - "# import os\n", - "# os.environ[\"HONEYWELL_EMAIL\"] = \n", - "# os.environ[\"HONEYWELL_PASSWORD\"] = \n", - "\n", - "honeywell_connection = HoneywellConnection()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### available devices\n", - "\n", - "Honeywell enables users to query for the available devices. Not all of them are QPUs: some devices have names finishing in 'APIVAL' (API validation) and can be targeted for the purpose of validiation or verification before attempting to submit a job to an actual QPU.\n", - "\n", - "We return a dictionary allowing users to see information about each available device:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "available_devices = honeywell_connection.get_devices()\n", - "print(available_devices)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### job submission\n", - "\n", - "Job submission can be achieved through the `job_submit` method, which attempts to submit a job to a device, and returns a job ID if submission was succesful.\n", - "\n", - "This method takes input arguments that need to be provided by the user:\n", - "\n", - "- the target device (presumably appearing in the list of available devices)\n", - "- the quantum circuit (assumed to be in OpenQASM 2.0 format, by default)\n", - "- the number of shots required\n", - "- a name for your job\n", - "- any other option as key arguments (see source code and Honeywell documentation)\n", - "\n", - "Assuming a valid API key, and no issue with the services themselves, we can then submit a simple job targeting their API validation device. This job should finish almost instantly, as it performs no simulation: the validation device simply pretends all qubits were measured in state $|0\\rangle$." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "n_shots = 10\n", - "target_device = 'HQS-LT-1.0-APIVAL'\n", - "\n", - "job_id = honeywell_connection.job_submit(target_device, qasm_circ1, n_shots, '1qbit_test_submit_job')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### job history and job info\n", - "\n", - "Honeywell portal / dashboard is probably the most convenient tool to know the current status of your jobs (see image in `Reference` section above).\n", - "\n", - "Depending on the timing of your REST requests, the job info may differ widely, from a failed job to a completed job with raw results included." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "job_info = honeywell_connection.job_get_info(job_id)\n", - "print(f\"\\n{job_info}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### job results\n", - "\n", - "The method `job_get_results` provides a wrapper to a blocking call, querying for the state of the target job at regular intervals, attempting to retrieve the results. \n", - "\n", - "If the job has successfully completed, the results are returned. Raw results show the outcome of each of the shots, in order. Later on, we may provide an option to return these as a sparse histogram with a given convention for qubit order.\n", - "\n", - "For any question regarding ordering of the qubits in the output, please refer to the Honeywell documentation.\n", - "In an APIVAL device has been selected, no simulation occurs and the result assume an all-zero state for each shot." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "results = honeywell_connection.job_get_results(job_id)\n", - "print(results)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### job cancel\n", - "\n", - "Assuming the job has not run already, it is possible to cancel it using the job ID. In the event that it is not possible to cancel the job, the `job_cancel` method simply prints and returns the error message and status of the target job to provide the user with as much information as possible." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "job_status = honeywell_connection.job_cancel(job_id)\n", - "print(job_status)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 3. Azure Quantum \n", - "\n", - "Though this package does not currently provide a way to directly submit jobs through Microsoft's Azure Quantum cloud services, the `translate_qsharp` function in the `translator` module can parse an abstract circuit and generate Q# code that can be written to file.\n", - "\n", - "This code is compatible with both the local QDK simulator (good for testing before submitting to an actual QPU) or by Azure Quantum. Submission through Azure Quantum will require the user to have an account on Azure, install the local CLI and Python packages if needed.\n", - "\n", - "For an example of how one can use this package to first generate circuits, and then submit jobs through Azure quantum, please look into the `example/qsharp` folder of this package.\n", - "\n", - "If you are a 1QBit employee, the following Confluence page may contain useful information regarding the use of Azure Quantum: https://1qbit-intra.atlassian.net/wiki/spaces/QSD/pages/1319600305/Submit+monitor+Hardware+Experiments+on+Azure+Quantum" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 4. Braket \n", - "\n", - "This package currently offers to generate a quantum circuit in the Braket format using the `translate_braket` function from the translator module. Users can then use the Braket python SDK provided by Amazon (`amazon-braket-sdk` to submit experiments to backends available in the cloud and retrieve results, or simply run them using the [LocalSimulator](https://docs.aws.amazon.com/braket/latest/developerguide/braket-get-started-run-circuit.html) that comes with the SDK.\n", - "\n", - "The following relies on the AWS CLI, and assumes you have an IAM user with proper permissions (Braket, S3 buckets). You can also use the Braket services through your web browser, which gives you access to managed python notebooks. This may or may not suited to your needs: deviating from this environment (installing new python packages etc) or moving data from your personal computer to that environment may be annoying.\n", - "\n", - "\n", - "### 4.1 Installation tips for use of Braket through the CLI\n", - "\n", - "\n", - "a) [Install CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html)\n", - "> aws --version\n", - "\n", - "b) IAM user: [Create access key id & secret access key](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html#Using_CreateAccessKey), then [configure your CLI profile](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) with them\n", - "(plug the requested information as prompted by the command below, or just press enter for all and then directly edit `~/.aws/credentials`)\n", - "\n", - "> aws configure \n", - "\n", - "c) Request from your admin proper permissions policies (Braket, S3 buckets), [enable Braket](https://docs.aws.amazon.com/braket/latest/developerguide/braket-enable-overview.html)\n", - "\n", - "### 4.2 Submitting and retrieving results\n", - "\n", - "Let's simply build a circuit preparing a Bell state, and show how to submit the experiment and retrieve the results using the `amazon-braket-sdk`. In the future, we may provide wrappers around their API in order to facilitate the process or ensure the data is in our standard format before post-processing. First, we use `qsdk.backendbuddy` 's `translate_braket` function to produce a quantum circuit in the Braket format:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from qsdk.backendbuddy import Gate, Circuit\n", - "from qsdk.backendbuddy.translator import translate_braket\n", - "\n", - "abstract_circuit = Circuit([Gate('H',0), Gate('CNOT', target=1, control=0)])\n", - "braket_circuit = translate_braket(abstract_circuit)\n", - "\n", - "print(braket_circuit)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Then, we use the `boto3` package, yfor your script to use your CLI credentials / account.\n", - "We need to provide the name of the S3 bucket that will be used to store your results: all experiments sent to Braket return us a result object for you to use in your script, but a duplicate is always saved on a S3 bucket for later use.\n", - "\n", - "We elect to pick the sv1 simulator backend for this example, to ensure low cost and availability for this code cell. The results come back to us as a data structure that you are free to explore." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Get the account ID, path to s3 bucket used for results\n", - "import boto3\n", - "from braket.aws import AwsDevice\n", - "from braket.devices import LocalSimulator\n", - "\n", - "aws_account_id = boto3.client(\"sts\").get_caller_identity()[\"Account\"]\n", - "s3_location = (\"amazon-braket-76c619ed45ad\", \"bell-test\")\n", - "\n", - "# choose the cloud-based managed simulator to run your circuit\n", - "device = AwsDevice(\"arn:aws:braket:::device/quantum-simulator/amazon/sv1\")\n", - "n_shots = 100\n", - "res = device.run(braket_circuit, s3_location, shots=n_shots).result()\n", - "\n", - "counts = res.measurement_counts\n", - "print(counts)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `measurement_counts` method returns an object of the built-in Python `Counter` class, which can then be passed to other functions: here we compute an expectation value." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from qsdk.backendbuddy import Simulator\n", - "from qsdk.backendbuddy.helper_circuits import pauli_string_to_of\n", - "\n", - "# Rescale for frequencies\n", - "freqs = {k:v/n_shots for k,v in freqs.items()}\n", - "print(f\"Frequencies: {freqs}\")\n", - "\n", - "# Compute expectation value for ZZ operator\n", - "exp_ZZ = Simulator.get_expectation_value_from_frequencies_oneterm(pauli_string_to_of(\"ZZ\"), freqs2)\n", - "print(f\"Expectation value for ZZ operator {exp_ZZ}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "agn_sim", - "language": "python", - "name": "agn_sim" - }, - "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.8.5" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/img/DMET_H10.png b/examples/img/DMET_H10.png new file mode 100644 index 0000000000000000000000000000000000000000..4e6f35b353f4bca3afa80abe6bdb4826bc85b898 GIT binary patch literal 40521 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%0DM=m!`CkkS52J#0xVgJ0B_;82 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

(XIcwJlF{HTB>9*Q@8` zG@`YK3M3e^019P0T0QTisPP+rakQ;O*K?+tWjW`3#Djyu_-kxuYHcb0u2b@q3|rt$ zUx1~;(GpY^@;(vc?b|H0$mR47liZ;`pCI;>u;`l*qicCzgx}CfvojD(q=bG9+va1P zVPjQf-v3Z87OfL=Uw=zfS(I?H(az<<@AAx3fm5Me4?v@GKzEccOHS+~{%rAu0>@oe zpYxeNE#OnYR)Al~G4A09%LCSUjhK_D~Mp%w)RX@iD7tq9pO|S#0G+^~4H_1P2 zpYfx{ChegwCL23Daz6NW9-zg=gw=tWHNJ+j9zLJFx&UZYdEd=^A-xk(L^|yXF1*d)M+m*AkgK_nv#t{_K7F zId1yjy4+#vRcf=~d1_E1lC!>G2QH5C`+h1$8Ja7ZDn+VW)8`w+$a`88i6JcQOf!3| zZ1BY6_H*j5RN$~6hdK|eP$OE?s^`y;^er(1X$~Jxd30-WOxnO(=IF*}j~KHZPQ`ZE z>{;{czV!`azW6mdA~3`c?yoRTZK@Qb#=0yvZ4yefIZ8E02f$Yr;#M!=2>UG|Fc`+s zN;@)u-Yh!xdeeaTGsi1ZVC3YKl)Z3xJq`;tK%n$|C);9T+7oNH7mgA* z5v;4cCTsDO6?yWqvAG#(tQjmKvT%OS^cskJ=sxT`GYGmi z=mW{hGcfhA0H_rLmp3i=z!b6(0uoeAoOOz`kx4b zkCu@nGYbKJ%=&s?m=Cb;-A;epZvOO~+%RtEE<{jAqZoMVM>3fNzI2%X6`yr!qOqaK z&oksWQ!Yo^`#{ajZl4s#Y>6|`f2|-LJ%IQllr=Mv*d$=#Dg|Sldd*p!Mlq={6~dTQ z^N}o==Xs#v@3J&Q7&E72l37EFPGMV+FHah`>24{;yLTAmp6(AV#h~A;fFqV#s+(@k z5$E#qy$ndy?*||;+b&kkaY|x%%2cDN;Dn>7-WW%e{dZ~7R?l9??Sy`A;>Te0{`;$6 zhk{qPw@fanStR(f!MZB1#a83o!mYCDS@MNGg|;?T>DAf`Qif2L-(wOzo&IJx9d^O~ zNgn(DeaNb`jXc|5v2^W|uNSkT_66vx=B!EfAJ`+dIvSs*%J#7RBsXSg(D$>)5u-oP zn_I9a^QmX&yg~n#6XrN4Q@&YZ_7Paz(2D>BTjWjEjqmMYP> z!(YEe*sgajEQ9NJM0AYxRv;-~Kd*Y@pmaI&-E^V_O0gj;okl>aRILL_ zN4=$8BxC*uw~MlBzna`|C!w&%6Y)gXz)SAT%-;xmG$^XQ&Bkt~=oJp3+GGWl_dD*q ziggIZ5cjtsvswrRX|+@S+rITu=kw*-7@s~xL{ko5dLU{wqdN2un4(9>o@6rj)@6=$ zHKR^_{);*-F6?7);NVjb)$08E^?@=xKH3wWyJfj-k%PvR5$Zx7QsW|u>m(&wz59_z zA$t0QseLO=Sq6qKSCDx`wx}`BliU{037ZezRQnJa^TNm`K4kF^dwa)oy5dc!jn1QbKg>qv zY7NBsNj{R!9*5__OoYB;rLr`auzenXe0)4V`Pf5MsZls=og@)iS>T5m7Z(@eYa-P8 zj-Nv9&6|y1)jB@M@6F@#Q(BODal`T9m%h~EAFxLfX%3^uM|wJUeY=DV>s}8d=)M~pKj+-CmZ1F0Z;#nf`Hk20eD&BhNWq_*P^@=7Eq6YPJ#hIhD@!k(P2bjpmJiI zPkpBDCK`;!N1FF`zrV1x`D$eIbusaHuOl%G9dENY1li{@cP$>cr2|i8ZXO;qNr1bF zSnd31y*rUFL$LEUhDtD!l0oM5jrwP4P{{T>WnApN?q4UcUDfzechg>XX>8;UOBC-; z_ELK>6qefHW>e|0u@LQXHbN83kERDiDOD>piOqzkL*`N8mAR+Nd0|xdm7AZx$_c8_ zo+OOkYcUWD+2WBt}*iewbda1rC=jIT1`@boEB{ z&9`fQ9dV%w8y$(R7GyB7c1~L(=bj&DKy@ZyxBgEfzkiuUrmO@Ws}NvdNww48Dq*Nk zRjr$}_9y1Oc!57O?@n+Mmf=t_wf3@^e$5=MZFN3X2(I}k;DQWNN<8*23D1xTNB(@~ z083kIeIGC5xk^u0NR5PzIr$ZQU$xZ%My*cM=84x(Tfc*)9VVXG#TUI32?mqcHe+N!0U!b3FGbwerRTTk!;5-))^0_@;m%xU zZ8`N!`C}1qWooWqyG|NHE;nrjtAs4>?*e0+73yhJ)26A4bFYeQZi}|d*8M; zHDFMxLG#c>a9>{e`k^tA)3D=3$B+e|auf27W)P(hH`NJYMnF4KI0%s7w{dw(S+)A# zV@*)KpUV0mG!@=P)3M4O9hL|k6LA8M!sxRQdp;}FLKDPw&q=JFc8#)7Lem+KB<+R7 zFf$i@B-A1bG~l}E{&2NFcyyJAtyA|!kWbiP!uDBKEa{Jj}3`xc83y`CpN$)`qtJKZTA&p0E{HdtXu0c7^zKp zSGc)2w#DLzf|#41KLm%v-y|I@$Cjx`&qCl>s09gzsXG`x6qI<-mvk(Y!B{>t(}Puq z^iV$@C<1P&#Rt+iSEV~Ry(JVbVoQZ7Ww_aLfA+*-i0MUY*414=A!^UR*#wOj@C5>Wf^FsGV)k}I zmew}H9T6eu-wgIFhBlT;y_2ja8jKwp)hjZ&7fMm%L019&=S3NXOE=sn7Zpk(m zQ1X~puxCK2MN2F#)_0&kF?ng<7MS=JM76iKgE$E1Hc>fZ&ACe+x@?UIA~?+!3=7!H zLo5StPgZlEzX_w10?nO+a@Dhd(+vAxP`A+S2b>JeI+UiFfD09h2t~v=f|eCY$Ra&8 zyf+(7g+{rF04Hj%>Ee&6Qe;U_Qr?t6y*4cN&95;+S9~!H9VOY4gABDQ_`9m(gY5meoZEeKSQ8)3*48w7aQ`tXn_=bo z-PXIN#zssP3Iv+{7L33zZBvlVsAN&Z&22k~gFLq?y*|hcORn?G1oksyYXKo!s*UYR zlcic;D|IOA9lsGE6u7HSBvd$nt@m-F*Sb-ny<;~qGwsOt_7COS&F~1R-v70iPQWin z%KX@Dq zA~Tpv{#3I!ZNh7H$_brSphskQh>@HbtOOj43G64_)V%5}Y*#3WrKKizBA&wo#d5vB z((6ILoqIFc3;_PdtN|IMoR;mQ-E$X~8}+huEW|yA8J%WgVua6~lB9w!GzO%aHM)%? z*Z%z*7_Xsj^{_b1(twmK60QTep@dugAIALr?Uon^$y&oW;e8Ht5w& zA^iJ+Nb0DlEnlfWd8o_xkavaOz#>&l`T84uLYFX(%8wa~Mq9l!w~HTnC?@pZ9JyPSc0e z=Z||>u|#Y=35RJ~4hFGsyv0VCh7dnIZcI${-}LnGq;cJO$tU)VjFKQDC1tYbZfRl8 zUs{ObDthJMD6)$+u-^1dvin)%-gn(&j}c z=3C-&;^PnEKIY-i{ZPLAp#%jCn&sA}wl>g+yO_#+ubO^uNu8N9-jUyssddh zEWF_3O2gpOh7uHTG_K>$RLKu}u^(>sXjZ!E_Ot`TQo8#6qL*^bxE}4admZFM%tWbe zyqpe+Jg3FhMtOCxX#c#yT{&~Pi;j!dffoYNzf}|kE5yYeTK>NslJPQd_y~}=U$ti! zw7TE9?X;TdVCE}T&We0`Ab)tk1l9wIGC}C#9y@2;PtzlEO-@&vU!;G^6>=R4J;la~ zgxB=+(0x|JH2D@ggDEqCQk?qcq}>Gf@%9u?d&vm`V%Vdj9*nODxu<34bBhFhi&c0{ zi1wOyb?SLwKxx;*{@b#WgK=^&M7I9P8Gk=AuGWCR-2<9LwJ1OlFak8mI#0TnZ+rQE z9SV_>*0crw00cf&%Ht8aApUAh<=JuaUm-UAEWCK%nUpd>7(WM9SPl7MgomT^nQCna zq57&LUnehG=sQ1hg?e)}ZNv0dKGHkx3R2?yL-Ctv5P(fVnScf;)PYcyB0 zqh{`aMy?)#K6iX`?|e%{mE*U*z6HYQ&2`^@c9ur^ng@~llomS{`(gGi{u^x^;e0{u zw_LiMgvm8-5Tc0Prd~3B2!uUCs)LCB#ndacimHc9xO-$Ru7@-s{z)t#i8)YTe^^W| z(o&N9oF*-?T0AE0rhoJD4p~^mk=LxH@qJcIfLQ|ViW7Nc$n)cEQ9}dSyGh9?$8Yi5 z+X_AfZ9(>DjV6UAO^G)utoqO&$_pPzFnzNt62arHsg5jev&bdbZ+a@POhJ~n3;@Eh2TeiGGq6NM?6#q?5$Xa7dgoR~y2GR^ddX>=Xh2mBG z5n6MRLeOLlEz-CDBB|HOdXV5aoKZuG>ks={X#j`?cm%LezY+fSGbjs&c6H)N(j(Nb zX=dUOOGw=m$X^{B#M2$mOSX)c#N^V8mr$+Mqt})&;h93s_=aB)3y%t`RgcHN&2O&% ze9LG~+656`7R=8d4zz70v2k#Uw2cOyo>ChODfm2C%r94A8KXaGUtv!=696yOkwk)* zz?2wbd_peH6!F0Lf0AGbGP=vXVA|v^ADBP%UJ;owseoRNMgl*^2F4(dW)a{)=&7n| z$d!@I$V{G88A2p9E-b2uxfHh>*+a7mPF5}l0!IOg9R22G2pO*nH!coGO(P3H#IR+9 zlsSlHzxsa6P{F-a7zo_pzkj(_S{UbCX|5EAF@ZUlhSU85i}d;37*R<`pbQMWEkJWp zb-Mq`>7g`tcr8tsH8v@dNEA<*{z>l~g57+e~As%%e8Z#Gh8t|+Nn?4cWKqs z)Yg~2y|-T?-O1F~{OB*e$8N}lCkyh*mzk{!lHAVK7qU`>kw`TJcu*IB-2u>|I-R~| zRqoUDw6BL_#EE3h*#-e!Yy9Z@laDEDY=lj*!Zxt6&E*qlCOjp=orE*S>HO9K>+JxB*-%Tz z8^SE557-zIC>Lo)`tI(CBcsp>1iv7=Z}};;wM`ye?azL<3Adomsjxil{$yxm96Iah zZp?UM;Hxm)pDGYd$TdtJ??s88L_4&l0qtA1cY7!mKzlPE2Da9`MqIDSuIN)Mw|MyW z&U9X;bXuM>VCiW0AYxNou}^F?^T{mxJb&nS5@mj=d|avaIat7YW1&g>wqrGsKHOrh`-jh7G5Y!a zWY8Zvi7Z5Ei)9|}F_~K10e5-D8+&mUe|d#*nUv+9-p&A$MGbAEi!5gNub)SE++Hbp zt%tIrqQY%+5W|N5`(&lC2qgRmI_~-ta5N(-NrT0Vxl7yq(XCWek!H{sBkE;=E z1p$QPc0?6Y9Y$mfGUq1r;Zg#CfojIVK4(}pMDW1=n-koFX`%DhmYsCgZIx8SH&yPF zyOiF=p?yZo-d>N0JLxG~jCb(^Z9Ua>D`60^BQ%|whM7Z=aRioOILX@sn#4l?i7%_k zt!>g_voRPGuiYa|4%F-8m3E@9fTNw7t;E>3U|vf}^h7UO^|-lU`PEKgsl*P}FT%6|uHB+yNb zsDGbInO3JA5zg^Y;3Seo5V^Ancl?AW=VkOUHKQz0*+1rq3}+j*t+;vEIO>;gfpN&c z9~8;Hh)EoRQxQL4L>mqMLma(Q2b*-(%N2HGCGAKm`8?>4q{{f8w;g$6N=sy~!@4b9&XT@RNS zY=*YEpdll_cHf&?9kuc6)p1jh(1n?CzLL#DsgAA8!ss3lOH8%*XAc^v3gsvyD40z_ zp#l5aw9@O;=bE6(9_iq77X$&B1C+_%YYm*^lc>mPy9EM<-=YHb0ugAjGf(7+4&W>R zbuF?6lbN8}2NUhH$zm%FyFovSl#tMeF~un-Qg&PRtha4m4kWX)EG@>+2nE)(Ay``F z`7a#}OJ84-YT~eZfjzNnf%zyV{ZxN-Zp}LCk-pwu1a&}`GT}KS5*Uou-G%<=JGbvZ zcquZiEaWx}(_t%)?VhLLgfFW&LQ2<6j|7}RrL^2ZZw6y@@_+-YBq>z{hB+U@X?Srn zW=l3UCdA5i`k#8=hsgTFf30z8jv=68kO2H4aMdWf6Ys(ECRe0976SH6@#@XGuR6DP z{F%|dNWdKeE89m#<4v@b`OUwybaW$spC9=|M100nJ-ovJ1kwWz0uW~&^A6&&-y7de ze|fRj>ZRC9`aM3nKNR>kHnv6I-}?eY{X!zLTq8~`&Y0t4YzIbBaCf(|nmTR&`Z@?Q zel~vKE~w(NCBHh-n4c_Oz#uCq>DO{D#9aMKm9~kR#RQ1CA%=Q|FXqyoB35u2qh_xnaR-hm;!`SeVqVbqQQhGE{#oNW52R+cxDFED}P>M z7DAI#F@oL3Tw;u21$4=#rojJ}rJx{yu_^YQHr}OB1fh4Ts41%eQa+|0TeS+KE+f~6 z?6I0$$2p2_lvL(VgiLbz%v=YBv8f!Q-1NM1N7IBX7VfF23qk|`sZn))sIEIc3__s^UF8a(`w6>yo12ox$yEbqZnyk?! zFF#NIGdM}$W}*aGK(}=SfJSnAe((mGcz{v1x9ONZt^@nS2$8V zTbVL$2N*${8o^12^9_JktJv)Ow7rN@Wz^KNc!;bexrD^0DQ_(ocF`3wS=>W!!Ww4A zj57T%YsyE=`pw@33*Pf~pPsaWBJG!D@Ru;c@9-t*24whqpSlvh38FJGeXQ8^5omlh zno)n)cG~?278Z`O`wDxL7`eeVHCsT|P6(t>II=|Rl}`8mBdLI2%o$ile8bGR_w&Eq z&Nl&jQ4U{&bQ^6sba&kJ<7xun#N7Cq;Q)Ne&j!6CH5#Jk2Q>eYpI zPck(D7J^z4Q3k@kv#ezG8Iroy>Aj*b5bPig+z>Ul`E8W{oM!M{2_~tOWYai+mnMqm zu*#6nZ4ZC;&Yl+^%Q8~oXcTZdkKTJ9xBb{5*u<8(7H z*vJ?wn&Y%E%F{g0fh5PfK6zSASi_&_1`yPUz;Eo^sjp-ajf)X}Xa*Nd%8l6-m(N10 zZLvG*f&6`)e@oqc^Q{4ZZVGxYF8JjU{T2l%HQZK!-{Sln1K_skQ?CRvR8VyQO&mBk z0FxPDLkoonzQqW~2QHhNz(4SBnSrMSP#>0#KQQO?%2h(;GhjA=-4FkS=oEJRH-?C< zvGw=hl^21hryeAi2l7hohT?D6VZ=*6?z==B zr?EhdZ5w+ph3mSXP8EMWAv$0Aj(zd=G78le4-qBYU}_aScFeC~$NCTK9DqC|g8PTao_uU3;o)dS7CKd3X1Yu1*+$lQIaR2qPft{W~6t4v?nu!hF{P&J7j3a2esbw zm))S$j|HL5qrU%sZ|&%egDZP6q|IT!$bA)pNREs=_4sIcbuBY#hZqBjPfXnWr&OCg z9~f-G*a`>RJ%Rew68`Pmx1t;r>#kXTKE4n@;M(YZJQBZv7`F8e9<|vcd&7=$)8s>pShpg2gPe+u)zd)XL~7CIMe}piFuq9RF9}#=neNw) z1Ya_#9ExoCNp3M=mL0Oi!2?lP|Cn~l1lF8!Qs56lsFM)Z062N_7I?khFV2e=$1yIN zXE3L+5pi`V_-qlg7)}o>H7-h7khyn0k1yNR02Lrj4AQCu4s*FfiaVK=mhh{>O?=k)N*b)+d zBbUf4awk2qY;V~`e>W;Fp(SK**SBanj{Wdd@`!;^ARfW*wkcQ*vieymu3M`^nKm17a?YNo0m6-rk~?%Bhd2y;&Ip+vmgY zB%-%eS@rviC?XMEAqrGnU&2hzry($kqELstnTPUTWmV+rQfJcXDAPM*u=84*O9>4C)aQ&@^~r3`t#!(hTnj zt$$UjF)k7%xf&oBxi zDIo5+8D&4P)|Y+!aG(S}NQz1sz%{M1k1CDX`VfBi-j^}F&zs}-ZznZ%JK@L5Zl*?H zC-+*P{Zp3_5rB^vc=2N^u7RSlxav({8@>N);h(jB|ABMrJL%tQ`+z!34g{qXeENVZ zAn@LeI##nU45Gm*2{Q|ITb>?CrXEI4eSGq0Xy|k#iTs+{AJqIrJF%srlP&_YQK=tM zAf)0IC5u;UOr3Q1F$+&pVKut&CHeuY+L!o)oJl3f+_iHF*9x1y-Ms(CNkaB*Nj1(y zYgy6XkMhgALe#f!w_q%Y9M)R57$6{cV6}#s%p}Th+x7L|Lgg2oWwckM8>DfKKwZn6 z93ZEVakN6JP9fN9Hz_et4T*Zm%&cki^A@DnE54>*(pMnyi}m{v;`akl1#tjunZvQy zvGZ1?B4_e;OqS|H%UmK-)8EMaWl zH0fiNk1xx?M@3s%1pv14?u@;EAl`1qnY4ENwT@=7oWd2n>+JiFAp7BeyXjf@NNxA- z-E*8i-p_T=HK*A@#hhb^cI-di_KWK$F0;p_d6zM<(E7uTrEhw9;=)ND?J;d1=_N~; z12puTQBnhB^M4vNCq>`M`LEJ5l-xmGrLQb_qBTWglN;F5$rlX^h`YB4#hGWAZ_&1X z$kVGuE`3I~lh{@fYWWz6P}&fp`Gc_G{+%2lMM3ACuf~BXYZPi}EJ^5OG+G8e+K6S9 zJ`FZr4@B`vBm?t6LlR^6KyR;r>{VQ-`ZrEmQs#3y^9xJ9`)>=D|1sxXRawO=C}m6$ zqAXg62m8|6f5WT=T@2Uo!Yf4<1!{>m|4wnDMGc1h?dQ7VH zuswFQTspaH6Rg~EzUWg$S~BKO^n$(i{90yNmO%8798dnOhvS&UMo)Gz)#=H><*H;Q z_lt_d%|-d5!$B!D6x7e#6Lf2KEvp2Ja|-;Lfw&0MLLY0iN)nk5WgAN_!CDZxA1A%d zP`Jl(Dy0*L)3QL-p;#iFRANjF2K4!A*5{zQ(ha~VAe0HXYZ>&006o$9;kdun?3%M{ zN3fx9eSN*vY0zo8@BZ@A=VnpV{0k=C)pV4v7e&x3F71uS{6XMoo;ZdrJAUH^nj9jy zlLfBic3>xCrzk#3@|k?+|!tk6d#J`iaaR^2Lm+ zd#iOVG&D-5*OGos56yB>CxPUdANUhYISv5JsGX7o7XnocZyMQk62aIbo?t^Pmz!QeKvpbK} z&j^*Ybmeie$ub|2&|i(u)r#O!VFgi-3?DcWWZL#YbpGzWSuMooz%$B@Q-febze-;E zN6hFe?;hTbcH3C7Xn-hR%hb8Z*Ska-vQ+C?;{YK4zSo>JYfr2#ipXLNt9qP#KlRL5WrHt<@Oz};wBHk@GzKWagQLVJhX3gI1<073lek=EQVVg}v zG|Tz9qLb$eQdhMMfwzxq;*V6dL~-vV#_4H zg?}l|dK$>?%`A;k@jMK{2j}p@tYuQsbS2Q_IuEgI1X+}`Mm-AfkOjIY(E#3{h<(hc z>%&%L=DP8Ats^)hLfgU(+qLytEuViQ^%+B%Q-6hRE(UNpBt)yZh-I*| zMxdg?Jj4SF)qzWpv3DxB@7k);oF57ohB6cKcBBTqnVMi8oVi#ll;pNYVUGLUpP|)^ zZ93I|)rxAo0)4Vm00t!WC+s`u_MGD++YwPR(5UDQ=Fwhi!^W7%-hY-p}9@9`k@i<^3 z?1_4Ts-0_Er09HThlb+5`kb^IweiFy)62x(P2#iYm!ye`i-#G1i8bNDlMy1nl&(fs zi78x!I+@G%6Vzy1s!R8ReO{-UlXmo&uv_Y0c=5x;S)$3F)R_-8VQD;EFB$1?ObvP9 zW3RXmXeQeGS$FasNZ~@P2dEi(Tzs)J-rtivt^;O1f``}JXdTCe$gM};35=GxtlF=L zN1DLaf1rO-wPF3k4iHZ73iF5i`)hus#pR3*Q0rS-_hm&bW6<64`^EytdNk#7?Hqr- zd0`lP1H&k`)ak}MS?`D}H|hUI)uTz%ufgNG;n>WAviWw?VBG~#PH^6!C%@gn{KlT1?7$siD(B5! zxcnUF%WJF#-5`poCg_DVESysk7Oon=qgYQpCOiI}vCLC6JBL(jcCdp|q-h30v;S*i z+|gF%R?(_s@tk9^s|A80CkeHb^LIp6* z(QjK!NlP0AdvrK3`HAEopEvfTrV&XbxwIVPHO#d;R z#;+ERZ_#{uFdTWq+>@c!_>VaK^qvs0KJ(BAPBj^fANp^U0-E0?Rd0T!xV8&R91?5hziTKHN=12k7&zxp9t0b5)s1%rDtI zwIQs4xN54V*zX*nx-h0B)WszpCe)Xcx4ZvFaWI**i6uQIo}8%E7j$+62mPfAik*~r zfJg&@)_z3}D7*FlZMf2X!lZj#cHI>Idyjthkmc;O-{}nGdoZR$nz;+gpVbF0E+~0S z+R#Sad#SRkKI`x}q?Or32h_kJVCZL?IZHYzQz!X zyr*zsuK?oG`uB~`9WE80WvkL*C|s~$&Q>D5iGovR*Y^^afnJ5mVv z7RkY(A0vTu4o>-1Q;&FZ9SCo$VHwLufX*v4=_Aj21JPxF&|;oPrD7Ir-z-CAVbt#`yxSM?UtF@Xk1%-yTErvE8;fd)AYZ?F8nk#o$^m6gJvXjGQ{>9 z&}zXa_1l5R%p_OAI7v<+u_-eifNOH&?(A`@SBLfI?3sx^cZgDib3>lAuGdUSxB9(_ z_j}d5kJH}HZ_7OHmm8$Qf-Y)!_k6?0uXPbGu3A=N@g=@$qKia7<{A(2a~yr`LwY! z{6ZVSW#{zYzrheFbEcBf^yvT3g z0Mi+|B!Uh;;Ut?rPx#vGCB*!b6=C1TBOw#gmb_w>0$?*^RZ3$Z4k~!3g3ncuGn{sW z$6NH7VS7yXBQLheiCxY?5R`&;#9H z&{tI!n`Vj@=gPNfNm5V049*u{7Y@TpJI^0`?&L&#>Fa0$+#Ioa#k=vOp2m8Tdj399 zJPp&myb8Qs3j{fMTH8-&vI1-jdV~Z91Ne;q`zFVQ5iCOe@LJ4d9*3XSb>vCUy{?Y9 zsYMrsK3D8MkEu&0Z7xfFZ7$)>kw>aVZ!`CQlamYqdsx~mL|o&y_#l`BvxMa-n6i~P zJUU6+eM6+R@V{@fP8?j&)n}E-B+U_w3}{wmne7I#Gib>D!_Uj+>6Xjqwq4FY%h*Ac zd(Actzh9vtj$T6Nd5J%ihhBW0L@FRV;kyaLu$t@3D}OB>tit%V+Yg9S z)Z`#d;UA~JV$-dUp6B|&%{{SMt{i~AF~FInGP>gdgrBBN;v-Np6eWhuEPh3BdyQW= zHz(M#EaWr7M*+*H3vBr1_|1Z*i5ma5T(eMVO6HiGnD_f9Ks-mmB)AoBcQd`K2~aBQ zPo`C{_d2bkXZW2)l&OdsTsFPvAs)ihP-?^ZN0GT&-|7eRHfP}^eg2=0mLgyVUDn@S zHmw@L^Gm4*^?;4#$qlTv03r9)ax0Rc3*j|{X~Cz#k5beO`bgGtKknw~{ILEvHLr(P zDx#8iVzb-YUkGma9d1;XUSj;E#Dqr<0QfHNix@jhgrvKB;mtz81_B6fWMt%Jp)8VK z*WmVcmLQQvduW=USb%R!W!9CLDEuSSw+cXQnR5i1pvp~toY&a4L$O3$d>%=sY&oK4 zS2?n?tZa_0tBPXzNDRi(-9AjxEJKR*AUaO#>BuN~v$0(c$qrr)V)nEeH@Nz{eJV>4 zoZHoE!YD`aaUs@oN;m(HZ?l%T+Hb0T z7IgM4_d9q1Ybig_Txd}NAv#eFeSIgRM)Zu1>vS`DT{7h)WbKHBf6C4LT?a@7>2F># zl|BEJA&PjQrxU#j=K2*(Z_sy2tSaUw zkl(95Lu^VK`Hb(s>YEDS_R< zp^&U^GY(jRdd^H?sCmF91Ns!xPC|Tqd`+)jhM}zE_z0A`)W- z@;8~j9(*^TjfY)ZTPt%X$gKagK|i)yKxPNQP1u_b+xktksEY50?zD_lPJ5?Ot~{uM zMa-rCtADqsK22J8Js}Ysh8ZL2sFbu*o|?oFcv7;da3>}P5d%Pxwa-uwHfEzxVql=h zp60%bitf7xU7?cL%Yn;6=ODuj%$oUng>+zgj7OINzQ7a=PxwusdBNC5=Ux)ER;kTP z#<55hHxC+f>}S^X4*Gov-mNkj+hy0;H;8X~|8~f+~`q2}iQ=tLgqF5MfDc+xG%o`rt6&g?NW3%nUrRPKX z`j&+W=e#9MKAT4z`Ym*Nk}rC_vyB0Uh+RE#&TtUw-KyuphPnr$ z>3>boLH}quqG>*V-RwZrAkvOk`0zv#@VEkjCizdAakmn;UqJDI0T)20cJKHYP*yRZ zy3-~~({VcNZ2wC!Ez{-zuyqUnFZx&j^tJW@rB=75NS~Hdf8AAw)qZy2>M zB;`Dqxm0slL5Zwi*q|2qi!%eeoQV5-$B~!nT)1&vxNMx`)u(ZBsa(?nYM2xsB9UF| zEQUT2G1}A|qO`Ak>6lP<`@Dk7*Cr-_{M`&smrKi7F%PHRqPb2JRi#EMX8QtqM>w5Z zEudPb)(>Dd#yG$k>?Jndu;!IRHHU#npocMd%B9Oy9o@w?ZmK0CtKDQ$7~mUUY-?l> z$yKubO7IGzZG|4JS6l4!7s}7;PXV!372Fq22#}<@WMO@gpGiWU=83E(fkvnJZalvZ zjqxu=m}hC^KL76w=@C?p-Fkcn|Cz8dyZNGdOz4~z;8t>+utYcLuWTsgk!*tz&6AZi z^hn8HO*lgMpgZJW>{u{a35EtdDk2zAzIvK{xf}6$dcyuYj@OMNb-#-DTKl4{KFa)M zYjU&aFO11&d=>qCq~~gY9qw9>2DZk|dS`$z>;_cGil)F^A2$zdC5mh#_L4u%Q}dUb z$~&3uL>%S+e0{M)jDW@kbYRGSbZPk{;Uf)zLnl91!~oJLU^N4f6~J2JT#Ij6t$RNH z=%IBmaY{|cmtCgx1Akkkj;poM>i~`)HOf0Y5K*9EH`Fb=**iwyn;eA~$FESZ8_cJp zJ#nYG-=?R)YV1CzeCAefPdFN`LR+0?)0`FaHr=FYAx|+{Pq*EQ=BJ)g#pWL_diBP# z(wMy6z5A?$BdwvsA(Jg&`d;_NokB}pm8I!CBw*_(V|@ksFvtQP7DyZeCIkB%uSS8V za{c>85D?Tm6E3d2q&-3fm?Mz4IEfihd!aRtbVHHI^{rPveFpzCa}Hxq#?IQ~w9^q(nU!8C zb!!2P&D2)u3(~}#dnn)E+QOPqGcm)n~D3{ki91ljtr%>WN!A_|$)y z%QeRkG58RmS!Q%}Kfn-fhc4DM*PDhXl-8q(K zc#XWs`Nj4a5Vr#`M}LkQ{B(d8)oQDY&!u-aFA>{xXIxSjMvRX3AFj`hF`@JMj|esm zX_}4UbuQ#~jR|KWMn&oo{GIl_(4LR%n2AU$ynVeofD0tCYks#(e&>*F9HBXMcoOZu{|&g7v=i*XS3kS~f;cutlJw z#xUmSwk;RZi`nuM#Eqq%Hx@yLmPu--5j?4Va?q0a+T#1>yukwqm&!>_kc#-ebAcwT zAr<`EU<+lNpYE$5SdDP5+oy6)?Lh@rBLtH%S%(j#63dX6x%GeYz9Lva;FPzW!eeNT zNfvT-vjgOmzzsf41-w*JW6p+A>vqv>)^u(y5O_;;eI#(m1t>@jjfqJ_g>rv2>esq` zsh*Dt?xkyy5=pne4O&AQe?)&^-a5R{3f0m1zE=3O>KZ6o&<;&{d3r1yo_xMx`?rU6 z#3S8qW~c-IewPX{{+eKZch-4UKfij})vC%N_thCWoIbam&+AKc{yauS&cLI zl}x?U@-UF=i&(_u^-%z>VzS^KRux=-*vO4V#%neRv`||?c>Ke0jBA5AZPPeZ26gbs zmFTV*wcGkb@9>sQqSJeKkMcu(%`&6(1R##S)!F>RE&Z-L0X+5=s!;N)F^aRlFw_tO zV5O}*y4m?WfQScQB`>cl^^0~)DmJ4p2SU`w`VDZ)S>-`a-*k<48a~fgnCFUudDwD6 zS|rCD)*5PnRo5hHg#c7whlPS28xA_ekWl%gCtP>L~Y{7RgkJH z_!ro`8a*!*Pe*t^Xrx}m3wytkHEbcf`m_99ME}#sKwGt(0IVY^DJhUF8ygZ5Vqug` zR3_dYn^=%6r#hXS2JYeo5|Du)EG8zZ!h^ulK61Rm`0g2Ry z0v*+~Nagq4#S$ttfQbsqO?e}xz_hH)EO)`&WuSGTV14NASGS2FxBVQ|^L{0Wki}HX zp|B4Ei=#Zg+%cdXDSSFeDzRNm@xsbcJlh7wDhGYqj6(ng9??O=vkF(p7__mmfhp?^ zJugGHVt|9oh|?XpjhteSOOM!61=SKl#z`{mtzP%^-+{c1ah5<`TrRyNr>2_6+b!Q> z1!EH;AYifhKGJ(fkZHV zdkkO$9EiH$-zfgM?Ndq;*=;XvH^|=KHp37|RBU{{CMJ5HZ{G_{*{DB6G)m(dh)a=z zLX|>ktCcoK*02UbHi!-C`oWehZ1IU(WFyo)e>TH8D3P$gGP4nI`TDyzq_5z=#t;a# zJ*TZX>a>by%$+mWCG6e!i}dH^>^bI?5AEr+=ZO$u-TRg%aJsn1<%}9#znn1OF?fCS zAVc#@SxSJ67|H+w-?)8#Z`pK+HtDs6$Lf@DxdV_bN3pC7WEsl;x)eEYa@Lf!cnouh zuDHjf=bbou801Vb;UkTw#2(GqDmvB}gouMkuH`~`e0-*m*9nn!zJFY%AR^iV{KJz_ zPuv7WD#p`{ybEcbCGO1JibM(j4;w(LSLC5_U=zH^M2H+~nOn4pt%n+rbRO$`7Lq@L zDl0zpfxW;x-tnM#!q0B$f>M$Qrc79q1{=1fS+Ga=qixe|=QgMez%+muK5PpC_uCDQ zrL;#B&vI1yxbQ=%?FzDsNc!(jlDngb8lmQZlF^>@hq6b|Sy|Q_Fh5cqW zdCZ&g?l;vU8|{QUU52L5Z-cKTf9u>jDI+7%0L+Yzd`Xph%ezDiLiLEkbNuo@n+M8IT&uL7edo5yB zB_4B-S0b5LrfK1UcWW15pyp^uapAi~x>If0XX{bwb`ck{>@q+gh_X?u_m|lW-NCVZ zs%TVM0satNqF7XCg5kju9ZigE_cwytCy&_*dDx;%8>;r2!%G(FVjHQEBf@1FvAx6! zZXT~2lT4qG!nJ&9A&$#EA~V)%+0HX_4~5FE^ZzCwB~II#41WWnk`Ba8oy zQTiB3^jVQw9Xw@l^CvkCP5<2;yWP9p^5(b1!HB>>!`nhOhQ3siibe@SShWO@3_U%~ z2lx;|`81a37#jvu*RiP_ zF=O4K&dWyS6hKd=2``omxSA4z8!6=nCeahjn!q#Gm$1Vy?_xr;$u*2$irx%342pPbG#DI8i7 zyKVm3^vYvLm}B`}71m3|_vEUZC}dhj>m$-vC3X+5dQE&{9J4TEe*9?E9;Z)JPG&;7 znBS5y$1{D>{A;zVFhX=cf)w+^d%-i;;ExN(GL~)AK}RPN0d zXYafcy9LX)q?voaNHHC~;p_u8o0zCrD5TDQ4Esb+%6A9xpg zFb~ac+}Rj(`i?uMoyq|*ibyULVEg?ABZW@KjG7m4Mdfk=49e;@2S^ZrI8&s#GTHzNI1|MoL%rLQ0JSp&+I49jOEbNgH^mP@P^G&@DYh+Vk`dzzDJPK_}= zzN$oFSTK}ItOZMp2fAOE;An)`e&Ht;*8aG*?!Mf8wZ7$hEqoH^w~2T1G2@Q(P_&6s z{GxUFBv2^YjXJHT>Ve38?H*g~>f+;Z(2=*@n&-##=Fkc;4-Cd`ANS*>ZpzEnmp=@F zRi=ZCgjM#&J9S){hTtDX07H|6RWH#0ZAu6rJi9hi-weA-I5;2z7u-b?s$}MMBV;yW zaDDMt(*6vCew<4PEZ@;bg>kOkk5(xUh(ktXoS8jkyqQc4=_A?oV3OKP;Z|v(RaLwu z)mw36qmCA=1DF^(+pYM^wV(APW+8QC4xARhwgk&xhA3(C&124De2Ic@o{=n?oL?Pi z7jB#p_;8(mwBSQSL$mMhPDb396{AWgLxI6yN=QhDuO@r`vP#JW@88>QgOZ61gkOE! z_%`=d95u)k<>aKm-|^;Siyz=h+SpVYk_ibmB7tO2j0y0aFAk3Wgu{}A*r zCG$P$KI%e$8aY~PfzuxRmzjBpF_d3B_KE)Kd+!wh2o#BmpMZ3J9Vd7Y% z-&^tU_)(ox_Za!e16uXCg4LV5;m6wHki-mP|`cCg*;pZ84RhoPZ&Y^sb86l}q*F{VrO@E2;UnDkddIYS%fxShG2N1UfWdPJ1E-XkT~C_lz>o*FfCMTHUMh+#%9EfJsI zyxU?P&eeiqBa+Hp!qd7h&28n+q53JaZopt5RS`OPJGQ(Vae% zF#0m~@cfq|vs^7&JFhfLhWyhwKDcCH_Y25ut$+B}=%<#ihJ{R}ANX>+wUWPSbp8tlnXVd1E}JPYj-fed_E zF38vDh#Yx5fQ|sPTP$$SIevc$i73&dU;fQc4G2X4RO7dI=$Tes9l$i5bD(J0xr>dr z6rGmbBC?clcn%94+NgV843-9cRnuO15OhSv?|ywJ>gV;%@*|32+}f7!1~p=%$M$2+ zbrD`G>eGMizEt8@3w{C8TIO@2ZK@sx+&-;GJ_{wszh0&Her&r&X?xo8!xKO040C!q zX@$DrY~_<33)4O!iJfg0IPQ-#ft>mlj3OKn)^EP99%KHp(L12s2?XJxfUkinpb`X5 zGJXp1c1S9yr~YXUL0vd_36!MZ10>-<^a7e`FYj!YiCv5U3Rv*JtuHH0f%}f)8Y12) z=Z>m;ZBcRz0~<>}z9U9!0JmL7ot5LNvTzXD6D@Li5@2PHy#j43l#-!hqf4z=ow0bM}N6nb$^*&iS*%Nlu{Rbx# zqkGXRyT*D7=65Xw)RaSv$xZOV-n}E?K=JFVCrB_tg_zGCCaR6EP>5+#x_YR$0Nb(A zO>oFH?Dcr)X_5uz3q{2e*}l8}v3rES?M`e_trJTiB+h=xSq>m%w49({dtehM;4%&jZdd+xgO?h-^J2 zFt-6n5qR{LwSci=+_$#I09ue~Bf7zsJ3>$~vB-o3dQih94OvWE!}-5)HI$P@lkV^A z$eNhYdV1aD?uGiPHHIhV*~2F{cNxgT9srx*uVubSfydL8bg@N5gM zQEd^OXTaVu#h+Ka&J9cfP)x3GBWQTG^M!|ks!T0RyW)T<@DG1bJiA9uP7WciI`F3! z?t_Ym`1kfQAfRmJeHQG+(4Ed8Q5ow<$aNgZh;azQHyukCMerZ`!qZ*-CzBtwJF_iA z@(e>)aQN*$Yr1QI=0Du5+0$cdTjDPviqzw~U@<k+P%6j+C1zy;WzI^fDm&HjR1pfq8X|;k? z0g8lzH<`${#rPeRdS52}e?cbmA}W762;n)K-wY-fUXjIf-1rsw^FCxWG`7tkX@)fux5L6R*wxPCvm){vb>Ab$uB@>J#0k(DutK=L1)me5Lxcv{zYnz z&B(|&SMT?f0y$|Rht5Bx9Cu*z9o~%v8FGh-dR>}YTVuw2AObj8eFFojDt(!piQp}# zf;1RDHr|!Fr6t|=-;kSm2zIwjYE4oA$T0Wcs5EzpSDEx$^yScu`;~5jH53FN(YN=f;vTQ$(7kx) zzJ(n$B*3wa`h{tm&>5v$1%2F$_k3}WmIB1Dduh8^+l~dr9+C?j?Xn<)?a5y0V+n0kvyFz?CE{&8}9Pw9D0FXy(~#LvqvO@?u zxrVbRB|~8-Z^{=z%>1v@CE&*z7*GVB2(+H!7w?}j^Hv8=jp(s(I8IbjMA)r=cvoQ` zT=rG#5uJXFF=YPlb56KAdwt2MzV1w|J;kydL ze5a7Qti;_o^LmcC_#d-l@j-vW<67vevaIm1xABvdNp zAx-fEN2Z(IrJnB+IPj>vE=yO|dac4KJQ14MMfRk13lr^^EW@Ez&*(eG$LTObngJui z|KY*!Unip<*iE7GPD^F`tJywt5S+jF>-;Lm^S~gCT2IKS z(^7PogGSQ+xdvKTM$4$MR6NT>C=&sDgf{BOKkcT0o4{o>w%#$3llm>A{{nnb-*k@a zM~>It&kKy>NjUPY<{BMnaj9Pi+KSF=qfd&qD358sl zGAQC#{oYpH6A`@>i+?J^dE0E=k}N}R07md60iI^#ZJ0d;IMe}QdEEY8gRhSy=9a8{ zi_G1n#*}YCeL=Gni9ikd8v@#aFG^oi)6)K3gsf|Y*pt}}$^bsq_4v+$4?w@(0uNcX z`gVv_+GAU{Hsz214z~Fk*IF9HHjXGnRbSUUd*w0c2CMNkrCNk&eRpBb=oWk(Q$7ns zml67|zAwM(pJbPKUH*N{E~zIJ&B@W{Re{c;o>8vSm9qZegxP;&INu5ziI6FO50Uo5 z-!;d0&Y$~&TBF45qnP_LKQFI9h@lBBDJfZ%CTI4Hy}VSiQNGh|d*SQJwX&u_WT;VgKSAT=RUToXEy!gqeR9Ve@^WLLU4w=Q=+ti;T{aGEl|P4{K6Bt z&W_PwjK4U!uT`fy?H@KytgMhCg;-2kcQS|N<~)PG`%icT^|bQ)Uc z2ses>s{7wH6BurjS2~N|gy^FtCKX>pB(whYm9$F$UHKn$Skig&AQU)Iy^ch2`cl;@ z-KQ9b4^|ET{^eg&jHH}Kl==1M6_4(9dZ}Ruyg?@7dq99MHa4~b9_K_!ybk9oVtlK* z6Jf|)bz$cx(Q4+3ewQ~OR&&v0?q^-@v{z=m1ioQRwBPQ1vIliE%_*2oPvk|q ze<|RFhSq)5FQh!}?v}KKI{UCdwg3CJh&q5IHN#k)F8HbHI9;EGL(^zLdyS_ts43 zkXI(?amoZ<%Q|WR5na3OjnWr!!wubIVD_5NT~d{hG}|Td$E{`DR<2*5ulmhz$-zN4 zTOL4;rgQqR6H-sfrUlsR)tqsuD_BL_FCu$^F;E3ukqwhbdi}^@>yCk@YF+v1Y%9 zt@S6>w6*wD#l7ET|LPBa(cQ23-dlJPl9JE6zbG86Z9VjNkTOu4JM4ar+`z_01S}5( zg4ce&`MytxD)(24SF})(625sUh4R3wJd68kMk7U?V-J(pL+9tF+3Fp*118KB72jCs zls5c7mn-mTPrmgG3oOW;ul)pQP9hPVoszclNl~)HhD|6V@Swl%(`eI3azuibe?KX| z3It@{JVUuWO~eWQ^sFU**tGzQz{x!wO6j}|h-&b%$Nlg!O50CdGbd-8lD7YNsZ~NK zMi&H0NjoqaYM&tSIKW5NhC3fNAzBo{(MzvC4Uhy~o* zjk9?>`KBWo+pnar0pETGuv`GK`Co*hY60w;(NPL{?REnYWTa}%qF0TMJjwn!h1z~G z{-sz!*|R6c6a|iYii5f!L5)P|(1~&SK)6LUF|+(BpTbb~AW4`Zj<4tit0B*Ge$rPJ ztPVM!-inldp;|IDF;KfLZQ(q0*cQ2D+T4{3EqTwQ=Qj3Mfn_r^nKv>kOT_hnqT>g^ zBj2*JYP5Jin|5-qmo&>zX3!Wm5pR%kkSXoiv!@rg@(`&FZ1IifoMzQgzHQORH43vQ z*9a08tTV2Zs52Z=-4Z%p|kRKLTLRksCmVSdtoiBW_t{_A2jh z;(W?K*#ZFh1AfGN>>whJZpg3XLp&(m+KnUxZ{*g4co9VOf{p&k>Cvc+}Sb=dzxFppy#ycuacHVDA@1^rc&NFbDkLRqs6JPQ#01TKe5PmfpY z;AhsUN!&&^s|De^f?%M0y#JF7m{!*cZpXbzfN!q%>4-^xV%A?df8$D)LA@&d+Rh)| zi-ch&TutTsqQ|=RKvqi&0%2xXdQ3N2|NK5p?d4?qxW#t0@yG2KinT(Hro=b*m(Lz< zy~kgP#dci_vd@;E;8z^AGVoOq`f@(4-g#TvC2BH1lCAb$bonDzMYe~hZhIw z6Fk3bQg?=c$c4^yQ7^~yZ4F|~5W9zC+j;~2cSfl9N88m-yDZ zc4h9RGstESMbw&wSyN!E1gSz?RD%ACBgOG#5AHag{X~Us-;DE_x5H_iJ$by%ouKcR zf<(>fjfkhGCsarXQ$sQr1eSvHR+~M`xFSdQ?y3ID_)YBlx1NXXMp9AZ2Jb)Qa!%yk zo)b5J%bhojhW;@e#&NjD+K~>F7Z`MWU%dg^T1{XSfh=SfXvV|{q zuILOeO4y5EFgp8uu$Mr9K+r2s?g*cRWZT94w{D!MEA8Ws%`yM7R^)`zg9sA#WWKZ! zAyU#0yf0paXcoD{l&h9sF0nbGvISgR`8{1--=B@0+-zm!OzlKtlq#-829rFCwzs!m zqkTG|JprLCMuT`6R%xK(?>RpwrV;lIyt|^!nGur2M^R^pl1a{@e*3Jdke3gMqwH=% z%fsrN9pW7=SB(4uJDEDz`86q21uNFP8(O;IGngG!WE(THVRrn}mtl+$Mqtd@=TZe} zY3lge)37~YOqN0UW_@R(@aL0T3n@pdz2>y_hy1{pm{)#s|Hbvz*bOf>1dNWV61Gmd zs%zSysz81)i;hNerIX}k4~<6a{$MVx8Or`^Wvo9@L@OI1+)Nt^K>#qv#>UrR=lPd- z3vj|vau88%ICrt1yVzH`toEOH;^LMJEICSh;*Osx=6GdE)IEn_FfWl%i>WX(8>Z{M zG_E<|Sx|u3QN!tuaKDN#mxVCJPah_YqL#rhx73%Z8`Ru{=>{j!ZN=?|t~A-3Nwv1L zd?zrR7Ma;`S;Jsp#766Gz!i+1RarE7L!Fzi%a4$0%fLAlZ1EPPhxD2u&RC;@kmVRT zoMff1jM`+uuT%{YC5p7W3Nm~nIrPd`evXNC14y!yw|6uY`vuY)749oSC+6UezkjL3 z9?rGAZ+8mF`XU|*fEl~4t{eu3qc%6+HMkwonwJwL2UQ_Sf2D%ebUolkr^;?A{!RjshfsE%LGBHu%6?P# zoW z2-?Tv$%70yuO@2+x_pr0B3az(37^5k*YP}OAAI6MX`z#FfOd48X@`A-bv!4otf}-L z8BqZ=XCwr<9D>-`*vj_yIA9Y8cM$-Yz`8=FycS~`NlVyqw5{GB#wxzI$Vzg*-@s5+ zRaNKu0_#KnJN-7*)`@92$pE7}n79&_CPB_Xg$pBzDr&%${-ui*g9`XBb@s*7Y6o7{826nJSsc!pvERdpF0mAQaf6vZV)z*ewTwGw$iaqh( zDUCe7>pwkZz{SNq*`6R0@oNj~?2sldGILXUwp%16l%Yb4^g|Wh%j+&)vkQ)OLbj+H z{2A6GfOGPTveuX#{c_b2GE}|B$jM$!8)sOw^~eW<|F)PvXZWGI+ktE>E2F}mMC+Hn z{$&8;N_gjFGu1TB#rTMC+M(iGsGLUd%#4+-{b=i9+|pRA)mwefBhl|PG+j3JGjRpR zHhHe9a{4>XH?q8a!$sWH3i_LSg`-M{W+GDX4;1PU8BNg5UaeV{hmd4(Bq`||8g%{s z4Z5nND}uPc1RM1^SqKvC>!UybQRalDsfWjn6kPnSEu+PuW|=w3Qs|BP#GMs_pomD% z=qPSv`8SXDL}){u##JFNy^(U49t3fgpO*@k05{ALGi2Y3S~h->yT$8jMcfO`#vV7M zL~nI?tlM4kE5;1QyOa>KldT_V9W9M5C(Fv+P#`BhIq9ceUyl=4?OM;7V0+;;UUyKh zr2Yfs1A``9*V-D=pM!sY?4bU*jO@k3uCsgnee%}<$&ux3NUpBarQAc#Kr;qg0EeQ# z#q%KbDdLl&!#?iB93CO-Gm)>5(t(5$TN%q1+||;IKE*Dj7WMOuUII9O{+N|!u$?pJ zr~7I$9OK_yq%3(PQyFQc_}|zN5o8Lrsaoz1*$uBwrwQ7^oqtB;xh?m0FF)WW93e~m zVP>m-fo}DrvNr5l@^dm^nk$t@f42Uob%}G4c%*KK=9*xgC6pSy*ZEo0&`^WZI$Fw? zFP|kZZ*8&yERNPDScrxpBUjk&0N?ThU6d3AhNI&N*l_y zOP$upbz{~?!+GPHzVT2n77e50$G*-Xz+MQ2M!P7u-3x}|vs+wmU^F#1SN^x9(cp?R z`j;vi>yKf_{e8>HT4ZK#XjkQM6fZWimX-?tHym3#qs|EpEIeAV&cj1Q&sIrI1hq>X zZSTg`)|i6>YQT7&t3#YZNm{Lme#K<=VG+GY1p?rzrJBfz4GG4og z?DgrLO4QL9DRjq1Y4-Z0e^^|Oo$##wnXvunVrZnP#c93oy9bBv_y)MmGc2qx71GDD z!$z%`eu6ze{q9}`w^d|6JE4T?GJJQ~isl)bGqs$kkdd*PZLN%_Ue_r8sp;qP8l&X* z0ESR2P2?tDimtBXdLK?{c{z~f$KBi_S`jljC=!LjL_FC$Vklp6%XH(;V5?(Ay^ zWQh$ANQRiKn;Y4G!`P9t72R(QlqN&4r44u0P`L74HojmR35?XUeBlcI7fNz^N=Qoj z-^AWURegP27+%K8@iBRK7Oi}lvWrl`+>$TSRL$CU(k(9;gOm`HU?ii)Zp%_YVLj8g zd<0V%|0$H}QiF~7g+@sY=?*7tvEt^ydKYJmf$Z1vUfW{T7EONZ)2H1y`0wLAMXP~( zA7$>MbmC2+^^ern!z0A@ONNc-84DtzR%SbwcIU#>`+ir{%3NHj*s#MrRXs25ZV+J>hH6y7_r%w~TffDBVF?tkZYFUe$|OhO3%njf!=_0sTs_KZE$> z?rv#dQ8sKv2>SMBlaI4MfEMLh(3dKR?r3Z$K2rY=T(tDC>OB_fWXXMZhd!hsCMQ`b z{#b-q*@|;W0l4f^`-@X#;nOy-Ch)Qm&|38#6H08{h1b@am`qiCcBqq1y)1S3E}yBhMvIVu3gW^`UaHuYJ2cycffu8NP|? z88aetX4l-Dw&f>!#rFvbb_@+`TMVLjtDjl74-Yzxjbu-5;rbw#v7Yqo(z&Q7If)P) zN}!e0y$ap=4~)6KjtkAd!bU>|xTO7q1K5WTXj4;D1DV@H(fCMpcIK|GXg`0xXmEO9 ze|&nndg?xE#Z|IE&wnPRG-FrTw@;qwB+QggJpJ0-(v0O>F0Tr6b!ayt=NoFq*yg_W z;*P=JoUIM+MsXih4)?$PJ-D*|5+h&45>xR^4RO~`jobK(Q<{35nJinft@rh+0RK0K zC4}GYEcQjgHtn}0k+l>wPYdnzzpyyz?pU@ph%g8 zFV+Yar%(GXTz_#DI$!XaLGJ@O`|2~7isovmA{U7T7_WRMeH;m=p%l%7BG@u*+6&uJJF`T8g`8cv5 zh#e+->+|CBA<2aI{ZFSGuz80IH=jp>AHV;z1XJfs})inrpR+B6)GeM zUxPn#M5R0VMvZ^Z+w$KDqVdYX4niqAVY-hM0}A{&e|Ns0T= z%8A2voE<(rq!s>=WcA|{%@KpLIhzN}f}HTEtvQxMkJP;iY{O+BH>O0t=d<|Jvu;&+ z3{E-%El*5KvwcU+gR!V4#4&tr&!k8cNt8@=ZgX=B03vxex4bN0v8*Oi?7Qn#I98II zw0e2@vfQ{e^21r!^jgu;Cvv&t7Dzf-*I5!l{*gQ+5K>;Egkm@=lV#cqPZc-r7#ST| zow!)O>-n#r@oA# z#SHQ1SAo1Ro?25OGvI&jkMgXH=$tCY7;kCTqiXY}$&M;M)TE>G?g;r%|BeQ0Sig#3l=OU43mNVf96*ZD*J?yIU)6Z=Ov7bw} zYmRPuuMcf5ba)8$yQ=XOHX}$R9vo5Cjddl7nQ?IQVI8>A!HwB2ty%Qg(#+*%Fj@N( zdW2LK)3*_YgLA??G)7F-mZS_ba~Ong5wdl$9zb#Qc=xYZY?pr_H)Y+4buZHg4LIM$hUy`Ei zs9j!C30qB#AXbh|2)C3iaPcT-`_6H}u3?jJxac_wmWqoVdYOj*`VxM(2Eubu1$}kj zUuL#%X7doFWR6=9VB7smA~!S@Ep*>p7Nbz~?)CD!d7i|1q}#eKq=;n1H+)$ziqD-F z`<2LaUpa7t`qQDqiQ7+Q1y2?d@aLT0{-N0~?4dvIrK4sZK@ozp zdMufooY!DpUXTSb*`HSC9*xnBjf9c1d&xaP2ZH*BM!gfO^w|x2TFaw^HTmmwC@XI; zgh}7#Ck?XkYwq|l#+tsBM0oY`W$Z#)@^-|P`O2N*20PN^Ef;%efdWzMHJhdxMj`cRN%>$R6QDRu*CKq1gw+#8L8Dpx7G48C$8V`dO}F`awZ@qJ{KftL5QILgo$e5 zs+b)aOM1c6%3Wi*n%&C|K>`3-(Dm}ZT6AjwxwUy5E?+d)2HcimS9eDm5b0DYQ+{$b zJ@bT7`=BKMBjiQy(g_Ij9sY|=zH*%sNI~)TJojZQzE>-~5Hg-5ip->c__qSB=C2S9 zVyQuV*mmbtSY@f^hZEHp<*A|Si4xjGK4sAg7C3V{Np1FRY0Z)%-M7QsGXmq7pNuA- zu@)`@U6V0eoXk;a{k+%9PJZ52>Bk23+RQ?0TcW{pt-9ML5DE*6ut4^=rT^ruA!qu7 zqzH-c6T%Mqd?n{JCC)Gpx0#a@0XQ3pxXeJ8Ok`|`szbJle4KID1D5UeYNso*hs3VN zo|=}-(UYQ+-zW7$d<$Re&*V0fqVVhsXvU-(qn@XWkZ3a4EWNxCadBfc%F@l?wb9AR zgB%g1H@&hVKG*pDVPW&z$*y81N7_X*2vgcpY_a?a4SZ|uiiR=Xw7)HLt?lh7bqzCV z5-Lw0a(_q4RFk1VZ7K8D3%8S(7Ljth*dw)3IF_&p-x*9R(NRlKV+3qplLz@d=mq!p zvn{pZ1eFCZVRDx?Bx4LxI(p2M~NLX(a(GDT;kiHv#MAM{CDhnCJ z%0I#F(Eg67p{l$|M8>nmIeU42yMJ&hpbN_RA<%4vVD1af$1pOv^2DsclOibgTnLiVCf^4#QmD9i+|JHU&-ocAXeaQ&)3y0k zjQl=l>oOGw(kWnjTs$jo8mGWU67f4+Tj_|l$+FG;Db1Sq<1=-<@Kd&ev)GTa%Sa*o z2uEkaRS;yRmT_6xWY9OM`E2uaK7SM_o;mjRw!eR;JAU}Ng<*ZyZdjD5&uu<;&{fB& z(_>E_(Lm@_quIka+2aU#CJUiLud|XG7cp3tCk`F-p;kgct!gMHjVjJkgpizMpVc6< zd6I+<6hWlvuwgy32^`+!;#V62F>=R`r(`nV+H=%~wU_wi2b4_zADUdm_nv&y`L$L} zxkCkE{;1n>d3N7cDUwZjM4R~RoUZTRX1xAiO$#{RaiA+1~v@ z%nvDFR&l}v?dm72>Ybcjf_>|wB-`=C9}{ra&*M>&PmW z`E8AzC@M)%>aL-bt#`79_eJcvBAz!X?%%HHzug&w@$)+=kz{Dd%kn%V;*9^O&OCCGiCezrix)>^|@UA zvHzhy&8=<}$M5f8M=G`ha zHEHjM3yiw@ySceSQ>!ARe%{Xy1fleFDES{gPP~5I$yTS0?{ur9Rw8-l1#=?sYHlWv zk&CF%F~gTO>AG%B)MUVvz7EY|m--~T!(hC?@O<@746~Pky*HcijTGC7%#m=}Cr{!i z27SqR+rS=O@?xRNhH){XuS7_zk7Bp&chpLpmD~bknuc&8YFh^fRz}8C0i6nlnsd)k z?<*WGZ%KSpH@BbT!TX%rrH~2BZEw*Ee<)7HXsR1~wX=Mr@*T zFW#HAo1d-a+n1WnJ})t9E60048y-MS`LTnVBS$F>h_Jh_OJuXHGKGA?0_ps>JQQUx zf4S8ZA5mhItU3y^l#7h@kH&Va_v1D#;weD2u@@*5YRnohEN6x0gV zdxodIvpITg6{B~)a__%Zbto;xU$^O-8u@bPHvE~N57_bmK$T|%5-ke)69?x)5O(Z5 z6|#5e=;)BZ6)BYr@u*HyglAzRMe#3C)H%Pv^mSe3^u8kvLrU4CR_>0uumX;{qtSm* zI9-b%t@OXkBtcw=Pn8VLrk^MzSt0Y69+GS34e8cp{R%Ja+a>>_;mBT79dh(}_viHT zdO?NePk0K#kX(14OfxIMy@i*RwY8ycOO1gBW%o_u48yP21ywKa zh)^QxjoX*yi?o$~lRjq#^$qykK8VRVX{(f}fqy~9(&y*qd3h;9oR~O*w@@L0_#ovm z6xPRpE_nAMu7Olj;2RyyQn|JIMc}!ulb7d8rhM=AqZKhJ1{VfBHcCKTMf*~B#^W?N zMS}D42tlPd=`FMP7hoio?|0NJGt#s71=+|j9oOjxn>q~i4K(YB#xfY6*%2+6G9|du z<6|JcIitz(oRl#Pq0gq1Sh>^J*Y7x=P9l8sdAv8pS1FD%ba(n{N^4n9^++-^a;cV0 zSI+cds6F%im0vf~9-;FpaZnPWmzUSqcDPpS7x)T-LTnn_aC!Z!E!p2-LhHTUPQ0dM zc_+aD7vfGHD*SD|UB3X~;-HRH62~+Ub~hylh|*9i7OqKa*PqsLmtE=}eLYx)5V~mX zPPSZ`MK~-CEgu`h+yi&@>4M<$3eVATybClJRaKk7-B2Sb|C!5EY0aJ*nUS2TE#kVs zy8)V1M=_4d>U3S_H4Cd36YpWkY9zZ?lH$rQUg)$uJqnbTkM~8ety-3kUro2&a#ckl4%P^P-^7M98pIxhl3n<~)hkrC;vah_@fr=^`1T z<)pb7Qby78jy~HhI8wcOXB}{Vkkk)NH@==8R7TSHg@2%g4(B2W z!RY8zolUuhg1z`Ch)s*!2=9aAqubqY=nI8BDdPWpd#+sJwzAQyOWbOG_wX&o>g~V; z@k39TW>+=mFc;^_(h2W9G+0Ho-$`FIGP@^42om^yIaISN+y*Dtf}THmSVX{J1?f32 zrnEM=8dJ$XWE?SGKYAs7{0Iyk0=9OF*l5yJ)YPk>Ybh-3Wm&5VrMiVke~q%%P)Zn; zkLVbDC%pY3{4LRV3;s8Lu`OF7O(~xEv8clRgZB)jO3di*-f=W|d_qoEN*F=q;RO+N zfXqOS8wPkj|HCo3G#ia$Yo|d7P!Q#E5_D}yO!*V%7Cu!r+nDo9{dM|g@B(Z_ou=V8 z9v6aEmx?|#J9OrP+Bt0^V__CwD>*6a4mKEc1}$rrX-%Qz+4`Koo$(Qu^Wt#!{1a^q zoG3j#eQjroRXyVh2o2@%_2-sfRaLHJt1I|Gc1+#(esj5~saD<{)34lZx>9RRMor zDw?|E`x{(?GNSM4faLE>I5TNetGS1g5Mij&ru=aI9#yN(-l64~>Z{g|0asiJicZb0 zK{!`w*~t3k0Y_?jxzoh$JBcd&p2Uc`?C~A_xkawbDP96u zNpE#>3)^R4SIkY)H!^yo8Q4@AnGFFQ#e#wYup-Le`2&Q2%3^hB+p1Ng41O>F2-d#;a9_l%q!x)gmxH3pWnPTyg52wy< z{Rf_yM8%Y3`wS9-3p%n4EZn*%N%4|0B_t*WXWB+U#OoAgaY_rN4X>Y{fE*2}+n=NW zt>{0c9wYA=O^hLsUz7SRh`NC2g}P|+<5g}~H zkUr)eRlkKs$F)e(B*lY~n?D?Z47yCIE@z?!g>pF>d^YOK+<`b?(`+@y1Lde9t9oI( zI$Nee8T;(56yl~Qb$>yDdAeBax~Yc`^;O#=MGl>MRN^nsih?(lp30v|>02eH>ykSn z$u6Oh+SA1bvUwB!;ZY)t&tXWAxY+S#FS41dWwpOiA^46|l>a}MF>J1jn6M1i-6Kaw z=c1ybTDxutnwk>pQfC>94c5LS{(6?QHm*gzZWX6TG`&txxn;zC96xe8U zfB#DSoR6MuFRlEuAl$KI1(IlaBS^opq143bv5G;-ND-$^gSXf9}S|2p`Wcfkl%E6_b z_eGevgcL?XrzJ{%okoqFj+*QmTF3M`|86aX(7{Vwu(*UpL$>aYy=h+}fF**&_UC1B zEyag|e5m+~Am=K`ZqYEUL_s-GS}pQJS(!(r9lUr!phr3b_Ld0sSsrqXH_98O>#HO4 zY1?d&agZle==bj5^HmnRd+;@@EGRhjKjDd53i!-LI1)M=)(xA?la?(2Xdj1tx-B}0 zqobq$;-b;_^z<$$wqPvs7lXm|!&Wd0?uM$D%&6PEbgF6nk$bCP2v+iI;V3X4ioLpm zVIa$S2oUxU30QTO8np3n=&yg1NhG{c78ztfjdphDt@;wt=xCb znh=RRZK-+qszC}n-2e0{hX;PTdr>YXlou;$WH?b|QN%@CHOct4@rg{KnGDX##|VMm zpS}S&)cwZB*@r3%Uj{N&!?!7@v+#)n*U!g|G)~33CFT5_IA)i=(3!UrEAfD2gxG&-&$jBSoe~M@d^}uSpyJginOXaT<~FRItXUYes*XWB z6--<3aGpSu4_g`hO?rrm*i@E6s9pCXv_oK-BOWokO~F$3I1AQ+C#^tV+0quPl!J(k zwoBH-Khr?%tV4qhZSkYMJm-)8!jG#yB~@!6TWiq!=Z}&h6vH*GZhYu9`Z@&U|Bb5?!d6AjFu-hS% zd98wB9_|6sLtBLIQ14Dr5R^EcFItwwxr;q|_9sE{4CWHhmVzq_==uIv1_a3*%BK#9c?(UR|~#*(cd(6oPe*!$!l33;(}ivAic zUG%K7656$E?PP26x;$#n?Qmsd8`8aQ4jBqO@&G4mVT#hPE2+e(CVCXAS=mnbMByya z8yzhF=Shry616%^mD>VSZboD}w`lLNmCjTT6eFQeMq^~{TJPWFGkzkxqot?w62)%2 zC;YF%Z^Yc0TM%5Fk_Q!OAXpP;2uxJm2H3T72^hAw2S9ja4&7j0M7U9ua$i!l> z4mfme78DhgB@YEXSijXXjBb4J^PtWZeEk|R9_~{>t2+zVDAN8Xr#3q&cvunhy2SRo zoyq_O7g###WxAl}kjSV_sOJe2r+!Wb(99`cPPR)b=%~>1E%M|85K5&G-__f%PAoPH z+SN`=;bUp#~yIdQaC?HAaBo(HfO`e)?YowCppMn21mFFQR|{ zRyTZmBY78yZL)Vkv3o%Q^d$J9uzQc~oJFzD&QAKKMMPCKFLGS4-l3$13b8FyNFJ62 zU`z}nmnD4fVx5MDiHcrpfI2mW!^u4goW@}A-IhrAaft@x2K^Hifqm;}NfadjZ^3ot zjzx$Xy`bQw_KIlu9%2Yf>3c()WEyN$6@imc;Uh3fwf>ETKsUYz2jl9DTm&r*3kLB{ zh72V&SyI`w=@u1w+h{o-Y_tLZ#~ip~jFhq6VRCey?BB7%t2^i+s46W?jHstx@gQ_N zy5tqu%rvBVDpwvEHU;Kj@4NYr1Aiu{vDG<=O?yePZ%gutO;DK7z2t0%;`g6b+lM>Q zVq}$rDQ|1eW+@kTe~w=MSGkJQ4>_K5-+FXxQ!x`pHTl;B^f3b*y(qz?RlNzm2luo6 zn$ZzLq=x_ykI`Vv!}_QN?(0x;rnP)+cIMt3rZffp9n>687~2ewde83lH96SKdA@Va z?OixNJ^Fx7d^dbiW-h@-B;;PP2Q<$>YkGPQ&dJHCOm%+CcX@Sn(hkO(p-F_G!wYKH zfpyL4!McOo7ZIcyLI!nql$3hqnLeQB*dGV`y$9vuh%gO7*q1__J^I@@F^cP&E+Xjw zRbLcz>TDu$TThkh$Q(G6^#8?A(Dy@l=IgUkTl;KY(H|_7&asDbEe+%d*vtLLk3J1& zXX1^SQ~W9*DG7WyIfqyKVr-mFjv=7ZczC3XwJlQEwA?Tkq>@zXK|M<{(0+**np z480zyyy9pi(^x{m#kmb{3J%PHOrFfuKN$#2Lgz@)6l>nT`xNiWPw7IJR@`?&!9l2W z3$H;w|5Kuf^kytSzB2Mvq%m_g4>oU;7uo0Qzc+w(AcR=&?CcCAj&EPxMPN{K zk%M1(cCV9vB-c{=2!9&AaCa@zx)fZWK!hf^w2R$re2ze3i(~-(o^>F|jo19=l+Y}C z-xI@KzRjz+4PFFLI{&|Zx#cv@xn!ox@9F4?y6p&TgxnF@38~eb9Iw^J0uH~WW8oCH zdG3+^xx2G;Yj(62Ag}qQP_+L{nFx!6!*Y60@XvgwVo6BARwo%!tQC7R+Mgp$mJ3w- z#^~NW=HZI+F7D)MjgM`P5u{{vZ#S?J9v{V8fVzT|Z0zDpYNAZ3S1K<%TIkiQqC!EX z>TC@IKB@Um`7`Ly z52b5z1Z8()+8hnqUNaJ@LjRb-6vb;_+B!SyOHA#;jEviO5egb{8Z+Oe&ii}7{c7TV z>F~E>brko00z&Vfb(Di&G4OswqQYM@D54?K#FY4pWw1i?j~IPZjf0Pbos&3HU|R10 zN@~%-yn-ix7x}Fm{icq@`DekQ_z+c9==Gtnw%DVf)j5ot6m0j&O3a3h&ZrrFkFQ0> zrgkj=UV_go%*jZ3XLRpIwG-$=j4Q&XrnLUuRUfXF`|pn0#V_YHh*!bWZScC{2H|sQ zC(v25D#$GS(*;gZLv|qnM&{?wMBYyiSD?O?IU^Zf&t~iTnBfx&MASDoH!|7@1R3lS zBcTsgwA)ymT;69nG~!D=^IdAtkgeZa-7XBWcY};YK`BrXn3Shf)6{n_fJOITsvr0t${U*J0lIq- z724OJwsbH(XZ4#4yy`s_|hR_Ye%YUnK9>n(?k3g1DFxKBn)5qMiT{|hm zbGWGgSY+jJQEV5E6%zQ>!w9UG*P=0vwE`m7r*D|c!P)sqHlYM!Weuh6M4EJ=zp;pz zO9M$w?-yUN6{nNwd)krw7%RY1bl>^E_P#1A>Ne_kB$N~e8B#i=8$`N>4u>3?AtV%# zl928$1ql)9E|m~LTDn0%Bm@Npq`S|9@71|DH|OfR_}2QbrE6q{@yWgSFLqLyZzI1o>rUp=kywMs-Uye)1dElzD?}F)D-l%F(Y&O$!$ifSgOTNq8CLcC+5Mb@9iXdWV z0eH>MVjJeoXa3oX37DV*Bo0uu<^>v!C`74$z^M_QrFLi-7nMkrdH2;v+cjF+T#|&3 zDr6-Mw>cxpF^Z511t#IOpRfe$5MZWghHnWT*u{$&{v?oZ-B%=L47S%6GLdv<@DnkN0l1Ec9v-?Y`ougcPhj~M`3@owJ z+TZf~4|?w5a#6X+zgdOrlNM~G@s!)miI=6Sy`<^&)`0Yx5#8`#g){FrHwKzadzK@) zmH>e=e50}VKF^#}kjUJ9UAjostRSJ~vf0fgKyU>j@#|CRYg;Z&WsS}4?d5*nme7sH z^i!m+Ol8q3xJuh2o`F&dJm)dU<`gNyRW7Fu6%e7r(%1L%M(xT|9mdkK-xQQ9pG;B{ za+!Y)?h$es+b9@Xk9-Ox&Wx3#3_5e^dyFLGnhG=JYIiqHtHehcqAl~aV(8)~$cATC zqZiziTza)!ioeCO!Eh5#t5AQ>z33r33Di=t$ulLFuY@lGXBk(98_YrhH5v(_BC zhRCq?(n16#J8sXZimbodKW(>e^>mq5$47cX3LzS*CR3@9@y%XsyZ0WJ3pc-8 zO+)n(-s7;%mQzp?ntd@wD5`?gAVGdYKwlP(%Ql;J2b}U=V}lNNThmscIlc7t>2PE& z4gm08VNBRsqq}lXagtFK)m)rW&p}lRknezvM@UGBpt)_)c-wf9mY@geSv$PwEEay* z_>qqQvws6{dI#PMQdlf{JFwF0UIuS{pk?$hA_}zb#NvAD`zuHDk&&*iG!&VPMaJFm zV;>434yV}KVs-mUvGM^BWYu-5QnVL7=_xtTL5*9Dk^3aXsFg9?;Z z#>q2bMrVb%oar$ZUCuI=cNic&W<+^KTIR<}si#nr=}+`E;7Yr>nCZA`1pJ?r;8mzkSZu8Io3ld~Nhj zzSI7TXeg~rl%V}Av919OZYT@GvJZ`>HIVdjWmdu9Oy7i&yD7fAvrj1OY?GE9{9&hh z6pi(xhnYm@jG~C`t5gYI#&c&zsEO?2gDb;8bw;gEQjd+ai0U5yj**xhD^HmSHBG}2 z7XJH4RY)%5R^GP+TD#|NrA!k~$KZx%CusE15kMINXAB>`;Jc4?%VB(6G#-CeuvVw{ zoeXQT!XLtsCUm}TNR}+(ge{oPQ!?{Ye7BV=Gwx`U)?|K0DZQw%kj9xt4-M&qjrUvi zFylF|+1W^^`OsgYRp)vA7Z9GN28`FO4nkBn45R1ATc!u+m+JNJ%o5-o2zvqFk29U1 zZPruYowhq5_PVznAbNl4=Ey)%s2IgFB7B(9dSX3$hX_#Xt=pWQIl%$eyjItsVJ#*k zor0D%*T|n~8z|X~O@8WL5-G2CHdVz@)#I^a_tlQh=Kt3%3kM zJGoW$a%!#4z7cU9AE9y(tvpro>3j5;KUyqU!9;~pEni>rGiWJTLn&K49Eo_nk z?;f#8vqzJ_p$dOfohfd|o;2}kD2rJXvgwN&Wx7iIE@v%ww3|zQ zDs?fg(@-@6@8u&W0&T)xU(9RgE5W@c7zj`G3z|PMw;$l7j_@coiQGDf8_V=w`{Mb% zu^{(6h|DxOYEiqLLxvO^6nNwFB&wEB_`S;pWgZma=@WK;x-uP@_ZGcbLnAF_5B6Vu zk2&;V^1b$pj3W%9SB}u}{9=0WQc@QmTaOxs73s=|-|A&3b*kJIz$zeY#@WH9x`OY6 zDkg{zfxO`Q5Ncmo72NcI6}Rl|`{YlwtD!osZkxK&tpg@nh=;=+Yo~5x2IR4Tgb88M zURhn`2TeqYq)zB-^y!oIs(?+`izSDvQl1|mI8h-``xusHJ0-3mr-fL-P6EA9#l+;m zH(rIo5tiJt0UPo~XbJAOKC13x%NzQnjn@V_y1~X}D1}ir23@cFpV@tDkOJ+*X2h)r ze$zFcsVXsdVZqCubiBS;pq;%FViNo^#Ar{=0VJT_frx)EK` z_We<0tjt%(d{k*HSE36qn+!tzWKfQ4}+z z49VgY_w<6zoSX9QjUHgQU|Vmrlhl?<#s|2E_Uwxadj&P<0uh013s|Ut>?V!=3S#N4 zEvIku4Cbr((v|+DuX<0rG1#;Fp4QI-n6S%|e?P$5L1kE}ihAFVE`FIlnn}}p|M8{& z!9=myZ>`ZPz^zjsmgl?A@v7NZBcv|y6oCcBN|bv~95F)3K+mm4bBVyoDmCsP&LLLG zR|8bx#@5_K$r#QR5}gV%1ji%MN4vGc%vKbM)7<&c^1oWHMrQfBCUuTC`n+*8^KnWv zlNbel@)%YlZ-&9zxoi_p0isOk41TC3g%^g`&d05
EM7H0Fw$P`PqI6V?WsGJo? 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

`&L*Xi(9MN9n=ICPic52;CaRj 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 z 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\n", + "\n", + "In order to run this notebook, `qsdk` needs to be installed in your Python environment, or be found in your PYTHONPATH. Please refer to the installation instructions for any additional information, and how to install optional dependencies such as performant quantum circuit simulators." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['__cached__',\n", + " '__doc__',\n", + " '__file__',\n", + " '__loader__',\n", + " '__name__',\n", + " '__package__',\n", + " '__path__',\n", + " '__spec__',\n", + " 'logging']" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Pretty printer for more readable outputs\n", + "import pprint\n", + "pp = pprint.PrettyPrinter(width=160, compact=False, indent=1)\n", + "from pprint import pprint\n", + "\n", + "import google\n", + "dir(google)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introducing our use case: a ring of 10 hydrogen atoms \n", + "\n", + "In order to cover all the steps of a successful quantum hardware experiment for the purpose of quantum chemistry simulation, we decided to reproduce the results of our recent collaboration with Dow and IonQ on the symmetric stretch of a ring of hydrogen atoms using this package. It includes all of the main steps required for quantum chemistry simulation on quantum hardware. In that collaboration, the ring of 10 hydrogen atoms was chosen to demonstrate the viability of Density Matrix Embedding Theory (DMET) as a problem decomposition technique for the purpose of electronic structure calculations. The DMET pipeline enables us to take into account all electrons in the molecule while greatly reducing the amount of computational resources needed.\n", + "\n", + "The system studied in this notebook is characterized by a distance of 1.1$\\overset{\\circ}{A}$ between hydrogen atoms, and is built in the MINAO basis. The emphasis on this pipeline is to retrieve electronic correlation energy while leveraging the power of NISQ devices. A full potential energy curve has been computed in [[Kawashima et al., 2021](https://arxiv.org/abs/2102.07045)] to study the repulsive, equilibrium, attractive and dissociative regimes.\n", + "![H10 system](img/DMET_H10.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Representing the input molecular data \n", + "\n", + "First, we need to represent the molecular system of interest. Currently, qSDK accepts some xyz input that specifies individual atoms paired with their corresponding xyz cartesian coordinates. We can pass this information as nested lists containing the atom string and the coordinate tuple (see example below) or as a single string, specifying one atom per line and separating each coordinate with a space.\n", + "\n", + "The `openbabel` python package can be used to derive the cartesian coordinates from formats such as mol, mol2, ginp, com, pdb (...), and thus provides us with the ability to support your favorite format in the future, maybe even with your contribution to the codebase.\n", + "\n", + "Currently, this package works with molecular Hamiltonians represented in second-quantization, which is the formalism almost all proof-of-concept demonstrations of quantum chemistry simulations on quantum hardware have utilized so far. We provide all the necessary functionalities to obtain `SecondQuantizedMolecule` data and Hamiltonians in order to represent this system adequately from the xyz coordinates. The minimal input for the creation of this object is a nested list of atomic coordinates (a multi-line string can also be thrown at the `SecondQuantizedMolecule` class).\n", + "\n", + "This object instantiation includes the computation of the mean-field solutions, provided by a Hartree-Fock (HF) calculation. This is the starting point of post-HF calculations that introduce electronic correlation. Besides general molecular information, the `SecondQuantizedMolecule` contains data about molecular orbitals and spin-orbitals. One well-known use case for this is to freeze molecular orbitals with the `frozen_orbitals` argument, in order to reduce problem size, for example." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'active_occupied': [0, 1, 2, 3, 4],\n", + " 'active_virtual': [5, 6, 7, 8, 9],\n", + " 'basis': 'minao',\n", + " 'frozen_occupied': [],\n", + " 'frozen_orbitals': None,\n", + " 'frozen_virtual': [],\n", + " 'mean_field': ,\n", + " 'mf_energy': -5.27727228441684,\n", + " 'mo_energies': array([-0.71250664, -0.63494046, -0.63486503, -0.4162918 , -0.41611742,\n", + " 0.07821244, 0.07835203, 0.50868475, 0.50889667, 0.75894782]),\n", + " 'mo_occ': array([2., 2., 2., 2., 2., 0., 0., 0., 0., 0.]),\n", + " 'n_atoms': 10,\n", + " 'n_electrons': 10,\n", + " 'n_min_orbitals': 10,\n", + " 'n_mos': 10,\n", + " 'n_sos': 20,\n", + " 'q': 0,\n", + " 'spin': 0,\n", + " 'xyz': [['H', (0.0, 1.78, 0.0)],\n", + " ['H', (-1.046, 1.44, 0.0)],\n", + " ['H', (-1.693, 0.55, 0.0)],\n", + " ['H', (-1.693, -0.55, 0.0)],\n", + " ['H', (-1.046, -1.44, 0.0)],\n", + " ['H', (0.0, -1.78, 0.0)],\n", + " ['H', (1.046, -1.44, 0.0)],\n", + " ['H', (1.693, -0.55, 0.0)],\n", + " ['H', (1.693, 0.55, 0.0)],\n", + " ['H', (1.046, 1.44, 0.0)]]}\n" + ] + } + ], + "source": [ + "from qsdk import SecondQuantizedMolecule\n", + "\n", + "xyz = [\n", + " ['H', (0.0, 1.780, 0.0)], \n", + " ['H', (-1.046, 1.44, 0.0)], \n", + " ['H', (-1.693, 0.55, 0.0)], \n", + " ['H', (-1.693, -0.55, 0.0)], \n", + " ['H', (-1.046, -1.44, 0.0)], \n", + " ['H', (0.0, -1.78, 0.0)], \n", + " ['H', (1.046, -1.44, 0.0)], \n", + " ['H', (1.693, -0.55, 0.0)], \n", + " ['H', (1.693, 0.55, 0.0)], \n", + " ['H', (1.046, 1.44, 0.0)]\n", + "]\n", + "\n", + "mol = SecondQuantizedMolecule(xyz, q=0, spin=0, basis=\"minao\", frozen_orbitals=None)\n", + "pprint(mol.__dict__)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Calculating reference energy with classical solvers \n", + "\n", + "For convenience, we provide access to several well-known classical solvers, such as FCI or CCSD. They may be helpful in investigating hybrid approaches pairing both classical and quantum solvers, or obtaining reference numerical results. The latter help us quantify the accuracy of the approaches we investigate in the rest of this notebook.\n", + "\n", + "We find that our use case turns out to be a simple problem for FCI and CCSD, which can both be used in a straightforward way, by passing the molecule at instantiation and then calling the `simulate` method." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "FCI energy: -5.41008\n", + "CCSD energy: -5.40627\n" + ] + } + ], + "source": [ + "from qsdk.algorithms import FCISolver, CCSDSolver\n", + "\n", + "fci_energy = FCISolver(mol).simulate()\n", + "print(f\"FCI energy: {fci_energy:.5f}\")\n", + "\n", + "ccsd_energy = CCSDSolver(mol).simulate()\n", + "print(f\"CCSD energy: {ccsd_energy:.5f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exploring approaches using resource estimation and problem decomposition \n", + "\n", + "Now that we have the reference energy, we can try to tackle the same problem with a quantum computer. In order to do that we have to choose an appropriate quantum algorithm that is able to calculate the ground state energy of a molecule. In this tutorial we choose the VQE algorithm. Note that all the other steps in this pipeline are compatible with other quantum algorithms that can be used for the same purpose (like the phase estimation algorithm).\n", + "VQE is a popular algorithm, and due to it producing very shallow circuits, it is often the choice for proof of concept demonstrations on near-term quantum hardware. See our [VQE notebook](vqe.ipynb) for more details about this algorithm. Each quantum algorithm has requirements, i.e. its own unique building blocks and parameters. One important parameter to choose when working with VQE is the choice of strategy to build the parametric wave function (ansatz). At the beginning, we use the vanilla version of a well-known ansatze inspired by the Unitary Coupled Cluster operators in chemistry (a.k.a the UCC ansatze).\n", + "\n", + "With this set up in mind, we can leverage qSDK to estimate the resources required and the cost of this experiment. Later in the document, we show how by leveraging qSDK and choosing smarter strategies to build an ansazte, one could turn a seemingly intractable problem into one easy to simulate on a quantum hardware. Resource estimation helps us assert the feasibility of an approach with regards to device capabilities (simulator or QPU), or compare it to alternatives, including what is considered state-of-the-art.\n", + "\n", + "Resource estimation is important, as quantum computing is still a nascent field and the current quantum computers have modest capabilities (limited amount of qubits, low gate fidelity, coherence time, etc. It can be one of the drivers of our exploration, and help us identify the most appropriate approaches in our experiments, as well as their bottlenecks, where impactful breakthroughs could make a difference.\n", + "\n", + "Let's see what happens when we attempt to tackle our use case with the well-known \"standard\" VQE algorithm, paired with the UCCSD ansatz and using the Jordan-Wigner (JW) qubit mapping:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "VQE-UCCSD JW\n", + "{'circuit_2qubit_gates': 29184,\n", + " 'circuit_gates': 45030,\n", + " 'circuit_var_gates': 1804,\n", + " 'circuit_width': 20,\n", + " 'qubit_hamiltonian_terms': 3591,\n", + " 'vqe_variational_parameters': 350}\n", + "\n" + ] + } + ], + "source": [ + "from qsdk.algorithms.variational import BuiltInAnsatze, VQESolver\n", + "\n", + "# VQE-UCCSD heads-on approach.\n", + "vqe_options = {\"molecule\": mol, \"ansatz\": BuiltInAnsatze.UCCSD, \"qubit_mapping\": \"jw\"}\n", + "vqe_solver = VQESolver(vqe_options)\n", + "vqe_solver.build()\n", + "print(f\"\\nVQE-UCCSD JW\\n{pp.pformat(vqe_solver.get_resources())}\\n\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Resource requirements show here a quantum circuit that is way beyond the capabilities of current quantum hardware. Although some devices may easily accommodate 20 qubits, the limited coherence time will cause a circuit like this to return nothing but noise. Therefore, even if these many qubits are accessible, executing such a deep circuit would result in the accumulation of noise and would yield irrelevant numerical results.\n", + "\n", + "Even worse: emulating such a quantum circuit is already a compute-intensive challenge for our classical computers, and can be time-consuming for even top-notch noiseless classical simulators the community has built so far. The numbers of parameters to optimize would require running this circuit many times over, and the size of that space makes converging to accurate results a daunting task in the first place, if not impossible in practice." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Problem decomposition to reduce resource requirements \n", + "\n", + "Problem decomposition is an approach that can be investigated in order to reduce resource requirements, attempting to find the solution to the initial problem by solving a collection of smaller subproblems. This packages offers several problem decomposition techniques, such as DMET, which is the one we explore here (see [DMET](dmet.ipynb) and [ONIOM](problem_decomposition_oniom.ipynb) notebooks for more details).\n", + "\n", + "We decompose more and more aggressively to show the impact on resource requirements, going down to fragments of size one atom. Since all fragments play a identical role in our case, for symmetry reasons, we only focus on one of them and treat the others with CCSD to simplify output and calculations. \n", + "\n", + "The `get_resources` method allows us to peek at some metrics characterizing the fragment circuit, to get a sense of its complexity. What we see is the initial circuit for that particular fragment in our problem instance; we have not yet simulated anything, we merely built the initial objects required for the algorithm." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DMET-VQE-UCCSD, 5 fragments \n", + "{'circuit_2qubit_gates': 1072,\n", + " 'circuit_gates': 2306,\n", + " 'circuit_var_gates': 144,\n", + " 'circuit_width': 6,\n", + " 'qubit_hamiltonian_terms': 325,\n", + " 'vqe_variational_parameters': 14}\n", + "\n", + "DMET-VQE-UCCSD, 10 fragments \n", + "{'circuit_2qubit_gates': 4, 'circuit_gates': 20, 'circuit_var_gates': 4, 'circuit_width': 2, 'qubit_hamiltonian_terms': 9, 'vqe_variational_parameters': 2}\n", + "\n" + ] + } + ], + "source": [ + "from qsdk.problem_decomposition.dmet.dmet_problem_decomposition import DMETProblemDecomposition, Localization\n", + "\n", + "# DMET-VQE, 5 fragments of size 2 atoms each\n", + "dmet_options = {\"molecule\": mol, \"verbose\": False,\n", + " \"fragment_atoms\": [2]*5, \"fragment_solvers\": [\"vqe\"] + [\"ccsd\"]*4,\n", + " \"solvers_options\": [{\"qubit_mapping\": \"scBK\", \"initial_var_params\": \"ones\", \n", + " \"up_then_down\": True, \"verbose\": False}] + [{}]*4}\n", + "dmet_solver = DMETProblemDecomposition(dmet_options)\n", + "dmet_solver.build()\n", + "print(f\"DMET-VQE-UCCSD, 5 fragments \\n{pp.pformat(dmet_solver.get_resources()[0])}\\n\")\n", + "\n", + "# DMET-VQE, 10 fragments of size 1 atom each\n", + "dmet_options = {\"molecule\": mol, \"verbose\": False,\n", + " \"fragment_atoms\": [1]*10, \"fragment_solvers\": [\"vqe\"] + [\"ccsd\"]*9,\n", + " \"solvers_options\": [{\"qubit_mapping\": \"scBK\", \"initial_var_params\": \"ones\", \n", + " \"up_then_down\": True, \"verbose\": False}] + [{}]*9}\n", + "dmet_solver = DMETProblemDecomposition(dmet_options)\n", + "dmet_solver.build()\n", + "print(f\"DMET-VQE-UCCSD, 10 fragments \\n{pp.pformat(dmet_solver.get_resources()[0])}\\n\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The next calculation will be carried out with ten fragments of one atom (one fragment and bath orbitals), as depicted in the figure below.\n", + "![H10 DMET fragment](img/DMET_H10_fragment.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Validating the desired approach with simulators \n", + "\n", + "The idea behind DMET is to decompose a molecular system into its constituent fragments and its environment. Each fragment is treated independently and recombined at the end to recover the full molecular energy. DMET is an iterative process: at each iteration, the chemical potential is used to adjust both the total number of electrons in the system and in the fragment Hamiltonian. Through the adjustment of the chemical potential, we iterate until the number of electrons in all of the fragments, taken together, becomes equal to the total number of electrons in the entire system, within a user-defined threshold.\n", + "\n", + "The algorithm then stops and the electronic structure is returned. For more information, see the [DMET notebook](dmet.ipynb) on the subject.\n", + "\n", + "Aggressively decomposing this system into 10 fragments of size 1 atom returns resource requirements that seem much more tractable for existing quantum devices, and seem appealing. But we know nothing about the accuracy we can expect from this approach, which relies on VQE to solve our subproblems. The fragments yield quantum circuits of a size that is very manageable for classical simulators, which can support us in the next steps.\n", + "\n", + "### Classical simulation of a quantum circuit \n", + "\n", + "Quantum computers currently have limited access and capability. To study the applications of quantum computing on problem instances of reasonable size, we can use classical simulators and emulators in order to anticipate the behavior of quantum algorithms on real devices, in the presence or absence of noise. \n", + "\n", + "This package provides a submodule called `backendbuddy`, which supports a collection of open-source quantum circuit simulators delivering different performance and features. We are free to choose the most relevant backend for our use cases, thinking about resource requirements, use of shots, presence or absence of noise, and accuracy of simulation, for example. Our algorithms manipulate circuits in our own intermediary representation, and a variety of functions exist in order to convert these objects into popular formats, compatible with other open-source tools.\n", + "\n", + "The `simulate` method below runs the DMET algorithm using a simulator backend. We could specify the desired backend in the variable `dmet_options` described above when creating the `DMETProblemDecomposition` object. We however did not, and the current default choice is to go for a noiseless simulator: since our package relies on [openfermion](https://quantumai.google/openfermion), which installs [cirq](https://quantumai.google/cirq) as a dependency, `cirq` will be the default backend unless [qulacs](https://github.com/qulacs/qulacs) is found in your environment. Currently other supported local backends include [qiskit](https://qiskit.org/) and [QDK](https://azure.microsoft.com/en-us/resources/development-kit/quantum-computing/).\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "dmet_energy = dmet_solver.simulate()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " DMET-VQE 10 fragments: -5.40145\n", + " Difference with FCI: 8.625559E-03\n" + ] + } + ], + "source": [ + "print(f\" DMET-VQE 10 fragments: {dmet_energy:.5f}\\n Difference with FCI: {abs(dmet_energy-fci_energy):E}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Minimizing the amount of resources needed for the hardware experiment \n", + "\n", + "Both DMET and VQE are iterative methods: running all the quantum circuits arising in this algorithm with high accuracy on a quantum device would require a number of measurements and runtime beyond what is reasonable on the few quantum processors available today. A more reasonable experiment in the meantime may consist of looking at a single step, and reflect on whether or not this suggests that with more time and resources we could successfully run the whole algorithm on a quantum device, in theory.\n", + "\n", + "In the following section, we decide to have a look at the very last step of the DMET-VQE process, for a single fragment: it resulted in a quantum circuit obtained through classical optimization, which can be used to compute the total energy of the system. Because of the symmetry in our use case, the total energy can be calculated by multiplying the fragment energy by the number of fragments. Thanks to the previous section, we know that we can be critical of these results and compare them to the ones obtained with DMET-VQE with a noiseless simulator, or even the FCI results.\n", + "\n", + "The `quantum_fragments_data` attribute of our DMET solver object allows us to retrieve fragment information for those who were mapped to a quantum solver. In the case of VQE, this allows us to access the qubit Hamiltonian and quantum circuit obtained after running `simulate`." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Circuit object. Size 20 \n", + "\n", + "RX target : 0 parameter : 1.5707963267948966\n", + "RZ target : 0 parameter : 0.0005896947252139878\t (variational)\n", + "RX target : 0 parameter : 10.995574287564276\n", + "RX target : 1 parameter : 1.5707963267948966\n", + "RZ target : 1 parameter : 0.0005896947252139878\t (variational)\n", + "RX target : 1 parameter : 10.995574287564276\n", + "RX target : 0 parameter : 1.5707963267948966\n", + "H target : 1 \n", + "CNOT target : 1 control : 0 \n", + "RZ target : 1 parameter : 1.6568407597743045\t (variational)\n", + "CNOT target : 1 control : 0 \n", + "H target : 1 \n", + "RX target : 0 parameter : 10.995574287564276\n", + "H target : 0 \n", + "RX target : 1 parameter : 1.5707963267948966\n", + "CNOT target : 1 control : 0 \n", + "RZ target : 1 parameter : 1.6568407597743045\t (variational)\n", + "CNOT target : 1 control : 0 \n", + "RX target : 1 parameter : 10.995574287564276\n", + "H target : 0 \n", + "\n" + ] + } + ], + "source": [ + "# Retrieving the DMET fragment information computed with VQE.\n", + "fragment, fragment_qb_ham, fragment_circuit = dmet_solver.quantum_fragments_data[0]\n", + "\n", + "print(fragment_circuit)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Minimizing the number of measurements needed \n", + "\n", + "The computation of the total energy with DMET requires us to compute the 1- and 2-electron Reduced Density Matrices (1- and 2-RDM). Computing the entries of these matrices requires us to have a look at each term present in the fermionic Hamiltonian of our fragment, apply the same qubit mapping as used in the rest of the DMET algorithm, and compute the expectation value of the resulting qubit operator with regards to our `fragment_circuit`.\n", + "\n", + "It turns out that different entries sometimes require running the `fragment_circuit` and measuring the qubits in the same computational bases. Since the DMET energy of a molecular system is a real number, it also means that qubit terms with imaginary coefficients are not relevant. Using this information, we scan the RDMs to find what terms/computational bases actually contribute to the calculation, in order to minimize computation.\n", + "\n", + "Instead of returning them as a list, we agglomerate these terms into a `QubitOperator` object (a subclass of [Openfermion](https://quantumai.google/openfermion)'s `QubitOperator`), which also keeps track of their prefactor." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.25 [X0] +\n", + "0.25 [X0 X1] +\n", + "0.25 [X0 Z1] +\n", + "-0.25 [Y0 Y1] +\n", + "0.25 [Z0] +\n", + "0.25 [Z0 X1] +\n", + "0.25 [Z0 Z1] +\n", + "0.25 [X1] +\n", + "0.25 [Z1]\n" + ] + } + ], + "source": [ + "from qsdk.toolboxes.operators import FermionOperator, QubitOperator\n", + "from qsdk.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping\n", + " \n", + "# Find all the measurement bases that are needed to compute the RDMs.\n", + "# Accumulate them in a QubitOperator object to manipulate them afterwards.\n", + "qubit_op_rdm = QubitOperator()\n", + "bases_to_measure = set()\n", + "\n", + "for term in fragment.fermionic_hamiltonian.terms:\n", + " \n", + " # Fermionic term with a prefactor of 1.0.\n", + " fermionic_term = FermionOperator(term, 1.0)\n", + "\n", + " qubit_term = fermion_to_qubit_mapping(fermion_operator=fermionic_term, mapping=\"scBK\",\n", + " n_spinorbitals=fragment.n_active_sos,\n", + " n_electrons=fragment.n_active_electrons,\n", + " up_then_down=True)\n", + " qubit_term.compress()\n", + "\n", + " # Loop to go through all qubit terms. Keep new non-empty ones, with non-imaginary coefficient\n", + " for basis, coeff in qubit_term.terms.items():\n", + " if coeff.real != 0 and basis:\n", + " bases_to_measure.add(basis)\n", + " qubit_op_rdm.terms[basis] = coeff\n", + "\n", + "pprint(qubit_op_rdm)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "At first glance, 9 computational bases seem necessary, one per qubit term. However we notice that sometimes a single computational basis can be used to compute the expectation value of several of these qubit terms. For instance, the basis required for `Z0Z1` could also be used for `Z0` and `Z1`.\n", + "\n", + "Identifying these computational bases and mapping them to these qubit operators is equivalent to \"grouping\" Hamiltonian terms, and allows us to narrow it down further. This is not a trivial problem in general, and several algorithms exist in order to attempt this. Groups are not unique, some may be better than others for different reasons (accuracy, or reducing the number of measurements), and the scaling of these algorithms is of utmost importance on more ambitious problem instances.\n", + "\n", + "One of them relies on qubit-wise commutativity, and is shown below. If you would like to know more about this topic, you can have a look at the following references [[McClean, J. et al. 2016](https://iopscience.iop.org/article/10.1088/1367-2630/18/2/023023) and [Kandala, A. et al. 2017](https://doi.org/10.1038/nature23879)]." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Only execute 5 circuits, instead of 9.\n", + "\n", + "{((0, 'X'), (1, 'X')): 0.25 [X0 X1],\n", + " ((0, 'X'), (1, 'Z')): 0.25 [X0] +\n", + "0.25 [X0 Z1] +\n", + "0.25 [Z1],\n", + " ((0, 'Y'), (1, 'Y')): -0.25 [Y0 Y1],\n", + " ((0, 'Z'), (1, 'X')): 0.25 [Z0] +\n", + "0.25 [Z0 X1] +\n", + "0.25 [X1],\n", + " ((0, 'Z'), (1, 'Z')): 0.25 [Z0 Z1]}\n" + ] + } + ], + "source": [ + "from qsdk.toolboxes.measurements import group_qwc\n", + "\n", + "qwc_map = group_qwc(qubit_op_rdm, seed=0)\n", + "print(f\"Only execute {len(qwc_map)} circuits, instead of {len(qubit_op_rdm.terms)}.\\n\")\n", + "pprint(qwc_map)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Each of the measurement bases here allows us to compute the expectation values of all of the terms it's mapped to, by running a single quantum circuit. We only need to run 5 circuits on a quantum computer in order to compute everything we need. Let's put them together quickly: each measurement basis simply adds a few extra \"change-of-basis gates\", one per qubit at most, at the end of our fragment quantum circuit." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Circuit object. Size 22 \n", + "\n", + "RX target : 0 parameter : 1.5707963267948966\n", + "RZ target : 0 parameter : 0.0005896947252139878\t (variational)\n", + "RX target : 0 parameter : 10.995574287564276\n", + "RX target : 1 parameter : 1.5707963267948966\n", + "RZ target : 1 parameter : 0.0005896947252139878\t (variational)\n", + "RX target : 1 parameter : 10.995574287564276\n", + "RX target : 0 parameter : 1.5707963267948966\n", + "H target : 1 \n", + "CNOT target : 1 control : 0 \n", + "RZ target : 1 parameter : 1.6568407597743045\t (variational)\n", + "CNOT target : 1 control : 0 \n", + "H target : 1 \n", + "RX target : 0 parameter : 10.995574287564276\n", + "H target : 0 \n", + "RX target : 1 parameter : 1.5707963267948966\n", + "CNOT target : 1 control : 0 \n", + "RZ target : 1 parameter : 1.6568407597743045\t (variational)\n", + "CNOT target : 1 control : 0 \n", + "RX target : 1 parameter : 10.995574287564276\n", + "H target : 0 \n", + "RX target : 0 parameter : 1.5707963267948966\n", + "RX target : 1 parameter : 1.5707963267948966\n", + "\n" + ] + } + ], + "source": [ + "from qsdk.backendbuddy import Circuit, Gate\n", + "from qsdk.backendbuddy.helpers.circuits.measurement_basis import measurement_basis_gates\n", + "\n", + "# Creation of XX, XZ, ZX, ZZ and YY circuits.\n", + "# This is done by appending relevant gates to the quantum circuit representing the quantum state.\n", + "quantum_circuit = dict()\n", + "for basis in bases_to_measure:\n", + " quantum_circuit[basis] = fragment_circuit + Circuit(measurement_basis_gates(basis))\n", + "\n", + "print(quantum_circuit[((0, \"Y\"), (1, \"Y\"))])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Picking the number of measurements \n", + "\n", + "The more shots (measurements) we take, the more accurate the depiction of our prepared quantum state, which is then used to compute expectation values. On one hand, we would like to take as many as possible for the sake of accuracy. On the other hand, the number of shots is directly related to cost in time and resources of our experiment, which we need to keep within the acceptable budget for the experiment.\n", + "\n", + "It makes sense for expectation values of terms with larger coefficients to be computed with higher accuracy (i.e more shots), as the coefficient may amplify any error committed by the quantum computer during its approximation. Conversely, terms with coefficients that are close to zero may individually contribute less, or even not matter, depending on the desired accuracy of the final results. If the expectation values of several qubit terms can be computed using a single computational basis, it would then make sense to pick the number of shots by looking at the one with the biggest coefficient.\n", + "\n", + "The community is actively researching ways to provide good estimates, from simple heuristics to more advanced approaches that take into account the problem's specifics and underlying principles. We look forward to supporting more of them in this package, hopefully with your help!\n", + "\n", + "Below, we show you what the numbers would be for a simple heuristic stating that \"when we multiply the number of shots by 100, we gain an extra digit of accuracy\", coming from binomial sampling. We use that heuristic on each of our 5 measurement bases, requesting 2 digits of accuracy. In our case, since all qubit terms have a coefficient with absolute value of 0.25, the number of measurements this method returns is identical for all 5 circuits." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{((0, 'X'), (1, 'X')): {((0, 'X'), (1, 'X')): 62500},\n", + " ((0, 'X'), (1, 'Z')): {((0, 'X'),): 62500,\n", + " ((0, 'X'), (1, 'Z')): 62500,\n", + " ((1, 'Z'),): 62500},\n", + " ((0, 'Y'), (1, 'Y')): {((0, 'Y'), (1, 'Y')): 62500},\n", + " ((0, 'Z'), (1, 'X')): {((0, 'Z'),): 62500,\n", + " ((0, 'Z'), (1, 'X')): 62500,\n", + " ((1, 'X'),): 62500},\n", + " ((0, 'Z'), (1, 'Z')): {((0, 'Z'), (1, 'Z')): 62500}}\n" + ] + } + ], + "source": [ + "from qsdk.toolboxes.measurements.estimate_measurements import get_measurement_estimate\n", + "\n", + "measurements = {k: get_measurement_estimate(v, digits=2) for k,v in qwc_map.items()}\n", + "pprint(measurements)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We decided to go with 10,000 shots per circuit, as we deemed it to be within acceptable accuracy and mesurement budget constraints. This further emphasizes the need to come up with approaches that reduce the number of measurements needed, and algorithms that can make the most of each measurement, in terms of information and accuracy." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "n_shots = 10000" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Circuit compilation and optimization \n", + "\n", + "The gate set we use to express our circuits so far contains widely-used generic gates such as H, CNOT and RX, RY, RZ gates. But these are not necessarily part of the native gate set supported by the quantum device this circuit will run on. That is, our circuits must be first expressed in an equivalent sequence of native gates supported by the device. Furthermore, applying gate identities to these circuits may yield simpler equivalent ones requiring a lower amount of gates (or even qubits), which may lead to better accuracy.\n", + "\n", + "This overall process of compilation to a native gate set and further optimization of the circuit is challenging, and is usually handled by hardware manufacturers at runtime. Some document and expose these low-level functionalities through their API. In addition to that, there are several open-source projects that specifically target the issue of compilation and circuit optimization for diverse architectures or gate sets: we look towards supporting some of them in the future." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Submitting an experiment to a quantum device\n", + "\n", + "This package offers several ways to submit experiments on quantum devices. Below, a few examples of how it is possible to do so through quantum cloud services, and converting your circuit in the relevant formats." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Using QEMIST Cloud \n", + "\n", + "The simplest way to submit an experiment to any device available in the supported quantum cloud services is through QEMIST Cloud's client library, allowing users to run quantum hardware experiments using their QEMIST Cloud account credentials and credits. This is possible if you have access to QEMIST Cloud, and have installed `qemist-client` Python package. For more details about this feature, please don't hesitate to refer to our [dedicated notebook](qemist_cloud_hardware_experiments_braket.ipynb), and reach out to us about QEMIST Cloud.\n", + "\n", + "Below, a simple code snippet illustrating how to run 10,000 shots of the `YY` circuit on IonQ's hardware, through Amazon's Braket quantum cloud services:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'braket_ionq': 100.3, 'braket_rigetti': 3.8}\n" + ] + } + ], + "source": [ + "# Retrieve both these values from your QEMIST Cloud dashboard\n", + "import os\n", + "os.environ['QEMIST_PROJECT_ID'] = \"your_project_id_string\"\n", + "os.environ['QEMIST_AUTH_TOKEN'] = \"your_qemist_authentication_token\"\n", + "\n", + "# Estimate, submit and get the results of your job / quantum task through our wrappers\n", + "from qsdk.backendbuddy.qpu_connection import job_submit, job_status, job_cancel, job_result, job_estimate\n", + "\n", + "circuit_YY = quantum_circuit[((0, \"Y\"), (1, \"Y\"))]\n", + "\n", + "price_estimates = job_estimate(circuit_YY, n_shots=n_shots)\n", + "print(price_estimates)\n", + "\n", + "backend = 'arn:aws:braket:::device/qpu/ionq/ionQdevice'\n", + "\n", + "# This two commands would respecfully submit the job to the quantum device and make a blocking call\n", + "# to retrieve the results, through a job ID returned by QEMIST Cloud\n", + "# job_id = job_submit(circuit_YY, n_shots=n_shots, backend=backend)\n", + "# freqs, raw_data = job_result(job_id)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Using a cloud API and format conversion \n", + "\n", + "The utility functions in `qsdk.backendbuddy` allow us to convert our generic `Circuit` objects into a variety of formats supported by other open-source packages and services, such as Amazon's Braket and Microsoft's Azure Quantum.\n", + "\n", + "You can thus convert a `Circuit` object into the desired format and use the API of those services directly in order to reach a QPU or an online simulator if you wish to do so. The example below shows how to convert a `Circuit` object into the Braket format. Provided that you have a Braket account, the submission process is pretty straightforward, as demonstrated by the [documentation](https://github.com/aws/amazon-braket-sdk-python#usage)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "T : | 0 | 1 | 2 | 3 |4| 5 |6| 7 | 8 |9| 10 |11| 12 | 13 |\n", + " \n", + "q0 : -Rx(1.57)-Rz(0.00059)-Rx(11)-Rx(1.57)-C----------C-Rx(11)-H--------C----------C--H------Rx(1.57)-\n", + " | | | | \n", + "q1 : -Rx(1.57)-Rz(0.00059)-Rx(11)-H--------X-Rz(1.66)-X-H------Rx(1.57)-X-Rz(1.66)-X--Rx(11)-Rx(1.57)-\n", + "\n", + "T : | 0 | 1 | 2 | 3 |4| 5 |6| 7 | 8 |9| 10 |11| 12 | 13 |\n" + ] + } + ], + "source": [ + "from qsdk.backendbuddy.translator import translate_braket\n", + "\n", + "braket_circuit = translate_braket(circuit_YY)\n", + "print(braket_circuit)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Likewise, Azure Quantum supports a [number of formats](https://cloudblogs.microsoft.com/quantum/2021/10/07/the-azure-quantum-ecosystem-expands-to-welcome-qiskit-and-cirq-developer-community/). Our package provides similar \"translation\" functions allowing us to produce a `Circuit` object into a Q#, qiskit or a cirq format, for example (see cell below) . Provided we have a working Azure Quantum environment, our circuits can be submitted using their API as detailed in the previous link." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0: ───I───Rx(0.5π)───Rz(0)───Rx(-0.5π)───Rx(0.5π)───@────────────────@───Rx(-0.5π)───H──────────@────────────────@───H───────────Rx(0.5π)───\n", + " │ │ │ │\n", + "1: ───I───Rx(0.5π)───Rz(0)───Rx(-0.5π)───H──────────X───Rz(0.527π)───X───H───────────Rx(0.5π)───X───Rz(0.527π)───X───Rx(-0.5π)───Rx(0.5π)───\n" + ] + } + ], + "source": [ + "from qsdk.backendbuddy.translator import translate_cirq\n", + "\n", + "cirq_circuit = translate_cirq(circuit_YY)\n", + "print(cirq_circuit)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Emulation on a noisy backend \n", + "\n", + "Since many open-source packages support noisy simulation and hardware providers put out some information about their devices, you could be interested in performing the noisy simulation of your quantum circuits. In particular, this could help you get an idea of the performance of your algorithm on a target device or get a sense of the performance a device would require for your algorithm to return an answer within the desired accuracy, without requiring access to a QPU.\n", + "\n", + "We provide a general interface giving you access to several simulator backends in order to facilitate the simulation of such circuits. You are free to use the \"translate\" functions of `qsdk` in order to use the API provided by your favorite open-source package directly if you'd like, as this offers finer control and maybe more features. \n", + "\n", + "Below, an example using our generic `NoiseModel` object and specifying the backend when calling `simulate`. Here we show an example applying a depolarization channel to specific gates, each with a given probability." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "from qsdk.backendbuddy import Simulator\n", + "from qsdk.backendbuddy.noisy_simulation import NoiseModel\n", + "\n", + "nmp = NoiseModel()\n", + "nmp.add_quantum_error(\"CNOT\", \"depol\", 0.01)\n", + "nmp.add_quantum_error(\"RZ\", \"depol\", 0.005)\n", + "nmp.add_quantum_error(\"H\", \"depol\", 0.005)\n", + "\n", + "backend = Simulator(target=\"cirq\", n_shots=n_shots, noise_model=nmp)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'00': 0.2879, '01': 0.2126, '10': 0.2076, '11': 0.2919}\n" + ] + } + ], + "source": [ + "# Getting frequencies for each circuits.\n", + "freq_dict = dict()\n", + "for term, circuit in quantum_circuit.items():\n", + " freq_dict[term], _ = backend.simulate(circuit)\n", + " \n", + "pp.pprint(freq_dict[((0, \"Y\"), (1, \"Y\"))])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Post-processing " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In our publication, we used the DMET-VQE approach paired with the Qubit Coupled-Cluster (QCC) ansatz, in order to generate our fragment circuits. Although IonQ have their own automated tools to perform quantum circuit compilation and optimization for their device, they had a very close look with us at these circuits, in an attempt to help the quantum device get results as accurate as possible. For further details, you can have a look at figure 1c in our publication.\n", + "\n", + "In particular, the compilation and optimization process in that native gate set allowed us to realize that the circuits corresponding to the `XZ` and `ZX` bases were identical, up to a reordering of the qubits. This meant that only one of these two circuits was really necessary to run on the device, in order to derive the frequencies for both bases. This anecdote reinforces the need for our community to develop tools to facilitate gaining such insights, as part of larger workflows and more complex problems." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For the purpose of our experiments, we thus executed 4 circuits with 10,000 shots each on the device. Using the results, we derived all other quantities relevant to us. In order to reproduce the results studied in our article, we use in the following the values that were obtained from running the circuits run on the device. We use these values to compute one of the data-points presented in our work." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "# DMET-QCC experimental frequencies.\n", + "freq_dict = {((0, \"Z\"), (1, \"Z\")): {\"00\": 0.0093, \"10\": 0.0000, \"01\": 0.0000, \"11\": 0.9907},\n", + " ((0, \"X\"), (1, \"Z\")): {\"00\": 0.0047, \"10\": 0.0047, \"01\": 0.4959, \"11\": 0.4947},\n", + " ((0, \"X\"), (1, \"X\")): {\"00\": 0.2059, \"10\": 0.2940, \"01\": 0.2947, \"11\": 0.2054},\n", + " ((0, \"Y\"), (1, \"Y\")): {\"00\": 0.2854, \"10\": 0.2114, \"01\": 0.2017, \"11\": 0.3015}}" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "# Derived thanks to observations after compilation and optimization: similar to XZ\n", + "freq_dict[((0, \"Z\"), (1, \"X\"))] = {\"00\": 0.0047, \"10\": 0.4959, \"01\": 0.0047, \"11\": 0.4947}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can then compute the expectation values of the different qubit operators of interest by using these frequencies. For simplicity, we manually compute all 9 of them, from the 5 histograms. In our case, since all circuits have been run with the same number of shots, it is pretty straightforward. Note that because the term grouping based on qubit-wise commutativity is not unique, several of these circuits could be used to compute the output of a different computational basis: in our case, it means that we overall have 20,000 shots worth of data by combining the two relevant histograms for certain entries:" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{((0, 'X'),): 0.0012000000000000344,\n", + " ((0, 'X'), (1, 'X')): -0.17740000000000003,\n", + " ((0, 'X'), (1, 'Z')): -0.0012000000000000344,\n", + " ((0, 'Y'), (1, 'Y')): 0.17379999999999998,\n", + " ((0, 'Z'),): -0.9813000000000001,\n", + " ((0, 'Z'), (1, 'X')): -0.0012000000000000344,\n", + " ((0, 'Z'), (1, 'Z')): 1.0,\n", + " ((1, 'X'),): 0.0005000000000001115,\n", + " ((1, 'Z'),): -0.9813000000000001}\n" + ] + } + ], + "source": [ + "def get_average_histogram(hists):\n", + " \"\"\" Compute the average of histograms provided as a list, assume identical weights (n_shots) for all of them \"\"\"\n", + " from collections import Counter\n", + " \n", + " sum_hist = sum([Counter(hist) for hist in hists], start=Counter())\n", + " avg_hist = {k:v/len(hists) for k,v in sum_hist.items()}\n", + " return avg_hist\n", + "\n", + "freq_dict[(0, 'Z'),] = get_average_histogram([freq_dict[(0, 'Z'), (1, 'Z')], freq_dict[(0, 'Z'), (1, 'X')]])\n", + "freq_dict[(1, 'Z'),] = get_average_histogram([freq_dict[(0, 'Z'), (1, 'Z')], freq_dict[(0, 'X'), (1, 'Z')]])\n", + "freq_dict[(0, 'X'),] = get_average_histogram([freq_dict[(0, 'X'), (1, 'Z')], freq_dict[(0, 'X'), (1, 'X')]])\n", + "freq_dict[(1, 'X'),] = get_average_histogram([freq_dict[(0, 'Z'), (1, 'X')], freq_dict[(0, 'X'), (1, 'X')]])\n", + "\n", + "expectation_values = {term: Simulator.get_expectation_value_from_frequencies_oneterm(term, hist) \n", + " for term, hist in freq_dict.items()}\n", + "pprint(expectation_values)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For a more systematic approach on larger problem instances, it would however be better to reuse the map used for grouping qubit terms earlier, and \"reverse\" it: that is, associate each desired basis with the set of bases for which a circuit was actually run. Assuming we kept track of shots for each circuit, we could then use a function to compute all entries of `freq_dict` as weighted average of the frequencies obtained from the device." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From the expectation values, we can then compute the one- and two-electron reduced density matrices:" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "import itertools\n", + "\n", + "def compute_rdms(fragment, expectation_values):\n", + " onerdm = np.zeros((fragment.n_active_sos,) * 2, dtype=complex)\n", + " twordm = np.zeros((fragment.n_active_sos,) * 4, dtype=complex)\n", + "\n", + " for term in fragment.fermionic_hamiltonian.terms:\n", + " length = len(term)\n", + "\n", + " # Fermionic term with a prefactor of 1.0.\n", + " fermionic_term = FermionOperator(term, 1.0)\n", + "\n", + " qubit_term = fermion_to_qubit_mapping(fermion_operator=fermionic_term, mapping=\"scBK\",\n", + " n_spinorbitals=fragment.n_active_sos,\n", + " n_electrons=fragment.n_active_electrons,\n", + " up_then_down=True)\n", + " qubit_term.compress()\n", + "\n", + " # Loop to go through all qubit terms.\n", + " eigenvalue = 0.\n", + " for qubit_term, coeff in qubit_term.terms.items(): \n", + " if coeff.real != 0:\n", + " exp_val = expectation_values[qubit_term] if qubit_term else 1.\n", + " eigenvalue += coeff * exp_val\n", + "\n", + " # Put the values in np arrays (differentiate 1- and 2-RDM)\n", + " if length == 2:\n", + " iele, jele = (int(ele[0]) for ele in tuple(term[0:2]))\n", + " onerdm[iele, jele] += eigenvalue\n", + " elif length == 4:\n", + " iele, jele, kele, lele = (int(ele[0]) for ele in tuple(term[0:4]))\n", + " twordm[iele, lele, jele, kele] += eigenvalue\n", + "\n", + " onerdm_spinsum = np.zeros((fragment.n_active_mos,)*2, dtype=complex)\n", + " twordm_spinsum = np.zeros((fragment.n_active_mos,)*4, dtype=complex)\n", + "\n", + " # Construct spin-summed 1-RDM.\n", + " for i, j in itertools.product(range(fragment.n_active_sos), repeat=2):\n", + " onerdm_spinsum[i//2, j//2] += onerdm[i, j]\n", + "\n", + " # Construct spin-summed 2-RDM.\n", + " for i, j, k, l in itertools.product(range(fragment.n_active_sos), repeat=4):\n", + " twordm_spinsum[i//2, j//2, k//2, l//2] += twordm[i, j, k, l]\n", + "\n", + " return onerdm, twordm, onerdm_spinsum, twordm_spinsum\n", + "\n", + "onerdm, twordm, onerdm_spinsum, twordm_spinsum = compute_rdms(fragment, expectation_values)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, we compute the fragment energy from the 1- and 2-RDMs:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " DMET-VQE QCC 1 fragment: -0.53827\n", + " Difference with FCI: 2.736958E-03\n" + ] + } + ], + "source": [ + "def compute_electronic_fragment_energy(fragment, onerdm, twordm):\n", + " \"\"\"Calculate the fragment energy.\"\"\"\n", + "\n", + " norb = fragment.t_list[0]\n", + " mo_coeff = fragment.mean_field.mo_coeff\n", + " fock = fragment.fock\n", + " oneint = fragment.one_ele\n", + " twoint = fragment.two_ele\n", + " \n", + " # Calculate the one- and two- RDMs for DMET energy calculation (Transform to AO basis).\n", + " one_rdm = mo_coeff @ onerdm @ mo_coeff.T\n", + "\n", + " twordm = np.einsum(\"pi,ijkl->pjkl\", mo_coeff, twordm)\n", + " twordm = np.einsum(\"qj,pjkl->pqkl\", mo_coeff, twordm)\n", + " twordm = np.einsum(\"rk,pqkl->pqrl\", mo_coeff, twordm)\n", + " twordm = np.einsum(\"sl,pqrl->pqrs\", mo_coeff, twordm)\n", + "\n", + " # Calculate fragment expectation value.\n", + " fragment_energy_one_rdm = 0.25 * np.einsum(\"ij,ij->\", one_rdm[: norb, :], fock[: norb, :] + oneint[: norb, :]) \\\n", + " + 0.25 * np.einsum(\"ij,ij->\", one_rdm[:, : norb], fock[:, : norb] + oneint[:, : norb])\n", + "\n", + " fragment_energy_twordm = 0.125 * np.einsum(\"ijkl,ijkl->\", twordm[: norb, :, :, :], twoint[: norb, :, :, :]) \\\n", + " + 0.125 * np.einsum(\"ijkl,ijkl->\", twordm[:, : norb, :, :], twoint[:, : norb, :, :]) \\\n", + " + 0.125 * np.einsum(\"ijkl,ijkl->\", twordm[:, :, : norb, :], twoint[:, :, : norb, :]) \\\n", + " + 0.125 * np.einsum(\"ijkl,ijkl->\", twordm[:, :, :, : norb], twoint[:, :, :, : norb])\n", + "\n", + " fragment_energy = fragment_energy_one_rdm + fragment_energy_twordm\n", + " \n", + " return fragment_energy.real\n", + "\n", + "\n", + "# Compute fragment energy as core repulsion fragment energy plus electron-correlation energy\n", + "core_constant = dmet_solver.orbitals.core_constant_energy / 10\n", + "e_fragment = compute_electronic_fragment_energy(fragment, onerdm_spinsum, twordm_spinsum) + core_constant\n", + "print(f\" DMET-VQE QCC 1 fragment: {e_fragment:.5f}\\n Difference with FCI: {abs(e_fragment-(fci_energy/10)):E}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Error mitigation \n", + "\n", + "The next step in our post-processing is focused on error mitigation. Due to noise, the hardware produces a mixed state, which reduces the accuracy of our observables. The ultimate tool against noise is error correction.\n", + "However, employing error correction is prohibitively expensive in terms of the quantum resources required and out of reach of near-term quantum hardware (hence the \"Noisy\" in NISQ). Although error correction is not currently available, we can still utilize clever ideas and leverage the known symmetries of the input problem to post-process the raw results coming form the hardware to mitigate the noise to some extent. qSDK aims to provide a collection of noise mitigation techniques.\n", + "\n", + "As an error-mitigation strategy in our DMET experiment, we use a density matrix purification technique based on McWeeny's purification method [[Truflandier et al., 2016](https://doi.org/10.1063/1.4943213)] to purify our noisy state to the dominant eigenvector. This is an iterative method which imposes the idempotency condition according to:\n", + "\n", + "$$ P^{\\text{new}}_{pqrs}=3(P^{\\text{old}}_{pqrs})^{2}-2(P^{\\text{old}}_{pqrs})^{3}$$\n", + "\n", + "For our particular experiment, we can use this method for the 2-RDM since our fragments consist of two electrons -- thus the 2-RDM is the full density matrix, and idempotency can be imposed. In general, applying the technique to 2-RDMs of higher electron systems would require the more sophisticated N-representability conditions [[Rubin et al., 2018](https://doi.org/10.1088/1367-2630/aab919)]." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " DMET-VQE QCC 1 fragment: -0.53893\n", + " Difference with FCI: 2.081728E-03\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/valentin/Desktop/virtualenvs/qsdk_oct21/lib/python3.8/site-packages/qsdk/toolboxes/post_processing/mc_weeny_rdm_purification.py:68: ComplexWarning: Casting complex values to real discards the imaginary part\n", + " rdm1_np_temp[i, j] += D_matrix2_final[i, j, k, k]\n", + "/home/valentin/Desktop/virtualenvs/qsdk_oct21/lib/python3.8/site-packages/qsdk/toolboxes/post_processing/mc_weeny_rdm_purification.py:74: ComplexWarning: Casting complex values to real discards the imaginary part\n", + " rdm2_np[i//2, j//2, k//2, l//2] += D_matrix2_final[i, j, k, l]\n" + ] + } + ], + "source": [ + "from qsdk.toolboxes.post_processing.mc_weeny_rdm_purification import mcweeny_purify_2rdm\n", + "\n", + "onerdm_spinsum, twordm_spinsum = mcweeny_purify_2rdm(twordm, conv=1e-2)\n", + "e_pure_fragment = compute_electronic_fragment_energy(fragment, onerdm_spinsum, twordm_spinsum) + core_constant\n", + "print(f\" DMET-VQE QCC 1 fragment: {e_pure_fragment:.5f}\\n Difference with FCI: {abs(e_pure_fragment-(fci_energy/10)):E}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Statistical analysis of results \n", + "\n", + "Experimental data requires a measure of its uncertainty. As it is often prohibitively expensive to collect large amount of data on quantum computers for the purpose of estimating uncertainty, we generate statistics from our dataset using an established method called bootstrapping [[Efron, B. et al., 1994](https://books.google.ca/books/about/An_Introduction_to_the_Bootstrap.html?id=gLlpIUxRntoC&redir_esc=y)]. For each histogram obtained from our experiment, we resample with replacement from that distribution to generate new histograms of the same sample size. We then use these histograms to calculate a new set of expectation values, RDMs, and total energies. This process is repeated many times, and from those outcomes we calculate the average energy and standard deviation of our experiment.\n", + "\n", + "In our publication, this process was repeated 10,000 times and led to a result of 0.540 ± 0.007 Hartree." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Bootstrap DMET-VQE QCC energy -0.5392±0.0059.\n" + ] + } + ], + "source": [ + "from qsdk.toolboxes.post_processing.bootstrapping import get_resampled_frequencies\n", + "\n", + "# Bootstrap method.\n", + "fragment_energies = list()\n", + "\n", + "for n in range(10000): # Was 10000 in our publication\n", + " \n", + " # Step 1-2: draw random bootstrap sample and construct new histograms.\n", + " resample_freq = {term: get_resampled_frequencies(freq, n_shots) for term, freq in freq_dict.items()}\n", + " \n", + " # Step 3-4: compute expectation values.\n", + " expectation_values = {term: Simulator.get_expectation_value_from_frequencies_oneterm(term, hist) \n", + " for term, hist in resample_freq.items()}\n", + " \n", + " # Step 5: construct 1- and 2-RDMs.\n", + " onerdm, twordm, _, _ = compute_rdms(fragment, expectation_values)\n", + " onerdm_spinsum, twordm_spinsum = mcweeny_purify_2rdm(twordm, conv=1e-2)\n", + " \n", + " # Step 6: calculate the total energy.\n", + " e_fragment = compute_electronic_fragment_energy(fragment, onerdm_spinsum, twordm_spinsum) + core_constant\n", + " fragment_energies.append(e_fragment)\n", + " \n", + " # Step 7: Repeat steps 1-6.\n", + " \n", + "# Step 8: calculate the mean and standard deviation.\n", + "mean = np.mean(fragment_energies)\n", + "stdev = np.std(fragment_energies, ddof=1)\n", + "print(f\" Bootstrap DMET-VQE QCC energy {mean:.4f}±{stdev:.4f}.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is in agreement with the published results [[Kawashima et al., 2021](https://arxiv.org/abs/2102.07045)], which repeats this overall workflow for various H-H distances and thoroughly analyzes repulsive, equilibrium, attractive and dissociative regimes.\n", + "\n", + "![DMET published H10 results](img/DMET_published_H10_results.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Closing words \n", + "\n", + "This notebook showed how qSDK can assist us in implementing end-to-end workflows, which can lead to successful hardware experiments and peer-reviewed publications in the field.\n", + "\n", + "On the one hand, it highlights how problem decomposition can make larger molecular systems amenable to NISQ devices, in combination with pre- and post-processing methods aiming at reducing resource requirements or improving accuracy. Such approaches may play an essential role in applying quantum computers to the study of larger, industrially relevant, chemical systems.\n", + "\n", + "On the other hand, it illustrates the complexity of such experiments and the necessity for the community to keep developing tools that can be articulated together to cover elaborate end-to-end workflows. From a molecule, we have built a quantum computing experiment which involved numerous steps, some related to quantum chemistry algorithms, some tackling the challenges of practical experiments on quantum computers.\n", + "\n", + "We look forward to further developing these tools with the help of the community, in order to both take us a step closer to making quantum computing applicable to real-world problems, but also simply to give you the satisfaction of running a successful experiment yourself soon.\n", + "\n", + "\n", + "## References\n", + "\n", + "1. Efron, B. & Tibshirani, R. J. An Introduction to the Bootstrap (CRC press, 1994))\n", + "2. Kassal, I., Whitfield, J. D., Perdomo-Ortiz, A., Yung, M.-H. & Aspuru-Guzik, A. Simulating Chemistry Using Quantum Computers. Annual Review of Physical Chemistry 62, 185–207 (2011).\n", + "3. Knizia, G. & Chan, G. K. L. Density matrix embedding: A simple alternative to dynamical mean-field theory. Physical Review Letters 109, 186404–186404 (2012).\n", + "4. Knizia, G. & Chan, G. K. L. Density matrix embedding: A strong-coupling quantum embedding theory. Journal of Chemical Theory and Computation 9, 1428–1432 (2013).\n", + "5. Peruzzo, A. et al. A variational eigenvalue solver on a quantum processor. Nature Communications 5, 4213–4213 (2013).\n", + "6. L. A. Truflandier, R. M. Dianzinga, and D. R. Bowler,Generalized canonical purification for density matrixminimization, J. Chem. Phys. 144, 091102 (2016).\n", + "7. McClean, J., Romero, J., Babbush, R. & Aspuru-Guzik, A.. The theory of variational hybrid quantum-classical algorithms. New J. Phys. 18 023023 (2016).\n", + "8. McClean, J., Romero, J., Babbush, R. & Aspuru-Guzik, A.. The theory of variational hybrid quantum-classical algorithms. New J. Phys. 18 023023 (2016).\n", + "9. N. C. Rubin, R. Babbush, and J. McClean, Application of fermionic marginal constraints to hybrid quantum algorithms, New Journal of Physics 20, 053020 (2018).\n", + "10. Cao, Y. et al. Quantum Chemistry in the Age of Quantum Computing. Chemical Reviews 119, 10856–10915 (2019).\n", + "11. Y. Nam, J.-S. Chen, N. C. Pisenti, K. Wright, C. Delaney, D. Maslov, K. R. Brown, S. Allen, J. M. Amini, J. Apisdorf, K. M. Beck, A. Blinov, V. Chaplin, M. Chmielewski, C. Collins, S. Debnath, A. M. Ducore, K. M. Hudek, M. Keesan, S. M. Kreikemeier, J. Mizrahi, P. Solomon, M. Williams, J. D. Wong-Campos, C. Monroe, and J. Kim, Ground-state energy estimation of the water molecule on a trapped ion quantum computer, npj Quantum Information 6, 33 (2019).\n", + "12. Kawashima, Y. et al. Efficient and Accurate Electronic Structure Simulation Demonstrated on a Trapped-Ion Quantum Computer. arXiv:2102.07045 [quant-ph] (2021).\n", + "13. Bharti, K. et al. Noisy intermediate-scale quantum (NISQ) algorithms. arXiv:2101.08448 [quant-ph] (2021)." + ] + } + ], + "metadata": { + "interpreter": { + "hash": "89fcd33a61ce1c8ae91bdb4eff5eb93c6c13a9486366ee1fefd22c672d14276a" + }, + "kernelspec": { + "display_name": "qsdk_oct21", + "language": "python", + "name": "qsdk_oct21" + }, + "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.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/problem_decomposition_oniom.ipynb b/examples/problem_decomposition_oniom.ipynb index 710132134..44868d605 100755 --- a/examples/problem_decomposition_oniom.ipynb +++ b/examples/problem_decomposition_oniom.ipynb @@ -69,7 +69,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -89,7 +89,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": { "scrolled": false }, From c4da5588e6dff40f83f21564a12716803ca4c218 Mon Sep 17 00:00:00 2001 From: AlexandreF-1qbit <76115575+AlexandreF-1qbit@users.noreply.github.com> Date: Mon, 15 Nov 2021 03:37:45 -0500 Subject: [PATCH 08/68] Clean up duplicated code Openfermion (#86) * Removed duplicate of Openfermion code --- .../toolboxes/ansatz_generator/_unitary_cc.py | 367 ------------------ qsdk/toolboxes/ansatz_generator/uccsd.py | 2 +- .../toolboxes/qubit_mappings/bravyi_kitaev.py | 6 +- .../toolboxes/qubit_mappings/jordan_wigner.py | 286 +------------- .../qubit_mappings/mapping_transform.py | 12 +- .../symmetry_conserving_bravyi_kitaev.py | 11 +- 6 files changed, 15 insertions(+), 669 deletions(-) delete mode 100644 qsdk/toolboxes/ansatz_generator/_unitary_cc.py diff --git a/qsdk/toolboxes/ansatz_generator/_unitary_cc.py b/qsdk/toolboxes/ansatz_generator/_unitary_cc.py deleted file mode 100644 index 5ce1a90a0..000000000 --- a/qsdk/toolboxes/ansatz_generator/_unitary_cc.py +++ /dev/null @@ -1,367 +0,0 @@ -# Copyright 2021 1QB Information Technologies Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Module to create and manipulate unitary coupled cluster operators.""" - -import itertools -import numpy - -from openfermion.utils import down_index, up_index - -from qsdk.toolboxes.operators import FermionOperator - - -def uccsd_generator(single_amplitudes, double_amplitudes, anti_hermitian=True): - r"""Create a fermionic operator that is the generator of uccsd. - - This a the most straight-forward method to generate UCCSD operators, - however it is slightly inefficient. In particular, it parameterizes - all possible excitations, so it represents a generalized unitary coupled - cluster ansatz, but also does not explicitly enforce the uniqueness - in parametrization, so it is redundant. For example there will be a linear - dependency in the ansatz of single_amplitudes[i,j] and - single_amplitudes[j,i]. - - Args: - single_amplitudes(list or ndarray): list of lists with each sublist - storing a list of indices followed by single excitation amplitudes - i.e. [[[i,j],t_ij], ...] OR [NxN] array storing single excitation - amplitudes corresponding to - t[i,j] * (a_i^\dagger a_j - H.C.) - double_amplitudes(list or ndarray): list of lists with each sublist - storing a list of indices followed by double excitation amplitudes - i.e. [[[i,j,k,l],t_ijkl], ...] OR [NxNxNxN] array storing double - excitation amplitudes corresponding to - t[i,j,k,l] * (a_i^\dagger a_j a_k^\dagger a_l - H.C.) - anti_hermitian(Bool): Flag to generate only normal CCSD operator - rather than unitary variant, primarily for testing - - Returns: - uccsd_generator(FermionOperator): Anti-hermitian fermion operator that - is the generator for the uccsd wavefunction. - """ - generator = FermionOperator() - - # Re-format inputs (ndarrays to lists) if necessary - if (isinstance(single_amplitudes, numpy.ndarray) or - isinstance(double_amplitudes, numpy.ndarray)): - single_amplitudes, double_amplitudes = uccsd_convert_amplitude_format( - single_amplitudes, - double_amplitudes) - - # Add single excitations - for (i, j), t_ij in single_amplitudes: - i, j = int(i), int(j) - generator += FermionOperator(((i, 1), (j, 0)), t_ij) - if anti_hermitian: - generator += FermionOperator(((j, 1), (i, 0)), -t_ij) - - # Add double excitations - for (i, j, k, l), t_ijkl in double_amplitudes: - i, j, k, l = int(i), int(j), int(k), int(l) - generator += FermionOperator( - ((i, 1), (j, 0), (k, 1), (l, 0)), t_ijkl) - if anti_hermitian: - generator += FermionOperator( - ((l, 1), (k, 0), (j, 1), (i, 0)), -t_ijkl) - - return generator - - -def uccsd_convert_amplitude_format(single_amplitudes, double_amplitudes): - r"""Re-format single_amplitudes and double_amplitudes from ndarrays to lists. - - Args: - single_amplitudes(ndarray): [NxN] array storing single excitation - amplitudes corresponding to t[i,j] * (a_i^\dagger a_j - H.C.) - double_amplitudes(ndarray): [NxNxNxN] array storing double - excitation amplitudes corresponding to - t[i,j,k,l] * (a_i^\dagger a_j a_k^\dagger a_l - H.C.) - - Returns: - single_amplitudes_list(list): list of lists with each sublist storing - a list of indices followed by single excitation amplitudes - i.e. [[[i,j],t_ij], ...] - double_amplitudes_list(list): list of lists with each sublist storing - a list of indices followed by double excitation amplitudes - i.e. [[[i,j,k,l],t_ijkl], ...] - """ - single_amplitudes_list, double_amplitudes_list = [], [] - - for i, j in zip(*single_amplitudes.nonzero()): - single_amplitudes_list.append([[i, j], single_amplitudes[i, j]]) - - for i, j, k, l in zip(*double_amplitudes.nonzero()): - double_amplitudes_list.append([[i, j, k, l], - double_amplitudes[i, j, k, l]]) - return single_amplitudes_list, double_amplitudes_list - - -def uccsd_singlet_paramsize(n_qubits, n_electrons): - """Determine number of independent amplitudes for singlet UCCSD - - Args: - n_qubits(int): Number of qubits/spin-orbitals in the system - n_electrons(int): Number of electrons in the reference state - - Returns: - Number of independent parameters for singlet UCCSD with a single - reference. - """ - if n_qubits % 2 != 0: - raise ValueError("The total number of spin-orbitals should be even.") - - # Since the total spin S^2 is conserved, we work with spatial orbitals - n_spatial_orbitals = n_qubits // 2 - n_occupied = int(numpy.ceil(n_electrons / 2)) - n_virtual = n_spatial_orbitals - n_occupied - - n_single_amplitudes = n_occupied * n_virtual - - # Below is equivalent to - # n_single_amplitudes + (n_single_amplitudes choose 2) - n_double_amplitudes = n_single_amplitudes * (n_single_amplitudes + 1) // 2 - - return n_single_amplitudes + n_double_amplitudes - - -def uccsd_singlet_get_packed_amplitudes(single_amplitudes, double_amplitudes, - n_qubits, n_electrons): - r"""Convert amplitudes for use with singlet UCCSD - - The output list contains only those amplitudes that are relevant to - singlet UCCSD, in an order suitable for use with the function - `uccsd_singlet_generator`. - - Args: - single_amplitudes(ndarray): [NxN] array storing single excitation - amplitudes corresponding to t[i,j] * (a_i^\dagger a_j - H.C.) - double_amplitudes(ndarray): [NxNxNxN] array storing double - excitation amplitudes corresponding to - t[i,j,k,l] * (a_i^\dagger a_j a_k^\dagger a_l - H.C.) - n_qubits(int): Number of spin-orbitals used to represent the system, - which also corresponds to number of qubits in a non-compact map. - n_electrons(int): Number of electrons in the physical system. - - Returns: - packed_amplitudes(list): List storing the unique single - and double excitation amplitudes for a singlet UCCSD operator. - The ordering lists unique single excitations before double - excitations. - """ - n_spatial_orbitals = n_qubits // 2 - n_occupied = int(numpy.ceil(n_electrons / 2)) - n_virtual = n_spatial_orbitals - n_occupied - - singles = [] - doubles_1 = [] - doubles_2 = [] - - # Get singles and doubles amplitudes associated with one - # spatial occupied-virtual pair - for p, q in itertools.product(range(n_virtual), range(n_occupied)): - # Get indices of spatial orbitals - virtual_spatial = n_occupied + p - occupied_spatial = q - # Get indices of spin orbitals - virtual_up = up_index(virtual_spatial) - virtual_down = down_index(virtual_spatial) - occupied_up = up_index(occupied_spatial) - occupied_down = down_index(occupied_spatial) - - # Get singles amplitude - # Just get up amplitude, since down should be the same - singles.append(single_amplitudes[virtual_up, occupied_up]) - - # Get doubles amplitude - doubles_1.append(double_amplitudes[virtual_up, occupied_up, - virtual_down, occupied_down]) - - # Get doubles amplitudes associated with two spatial occupied-virtual pairs - for (p, q), (r, s) in itertools.combinations( - itertools.product(range(n_virtual), range(n_occupied)), 2): - # Get indices of spatial orbitals - virtual_spatial_1 = n_occupied + p - occupied_spatial_1 = q - virtual_spatial_2 = n_occupied + r - occupied_spatial_2 = s - - # Get indices of spin orbitals - # Just get up amplitudes, since down and cross terms should be the same - virtual_1_up = up_index(virtual_spatial_1) - occupied_1_up = up_index(occupied_spatial_1) - virtual_2_up = up_index(virtual_spatial_2) - occupied_2_up = up_index(occupied_spatial_2) - - # Get amplitude - doubles_2.append(double_amplitudes[virtual_1_up, occupied_1_up, - virtual_2_up, occupied_2_up]) - - return singles + doubles_1 + doubles_2 - - -def uccsd_singlet_generator(packed_amplitudes, n_qubits, n_electrons, - anti_hermitian=True): - """Create a singlet UCCSD generator for a system with n_electrons - - This function generates a FermionOperator for a UCCSD generator designed - to act on a single reference state consisting of n_qubits spin orbitals - and n_electrons electrons, that is a spin singlet operator, meaning it - conserves spin. - - Args: - packed_amplitudes(list): List storing the unique single - and double excitation amplitudes for a singlet UCCSD operator. - The ordering lists unique single excitations before double - excitations. - n_qubits(int): Number of spin-orbitals used to represent the system, - which also corresponds to number of qubits in a non-compact map. - n_electrons(int): Number of electrons in the physical system. - anti_hermitian(Bool): Flag to generate only normal CCSD operator - rather than unitary variant, primarily for testing - - Returns: - generator(FermionOperator): Generator of the UCCSD operator that - builds the UCCSD wavefunction. - """ - if n_qubits % 2 != 0: - raise ValueError("The total number of spin-orbitals should be even.") - - n_spatial_orbitals = n_qubits // 2 - n_occupied = int(numpy.ceil(n_electrons / 2)) - n_virtual = n_spatial_orbitals - n_occupied - - # Unpack amplitudes - n_single_amplitudes = n_occupied * n_virtual - # Single amplitudes - t1 = packed_amplitudes[:n_single_amplitudes] - # Double amplitudes associated with one spatial occupied-virtual pair - t2_1 = packed_amplitudes[n_single_amplitudes:2 * n_single_amplitudes] - # Double amplitudes associated with two spatial occupied-virtual pairs - t2_2 = packed_amplitudes[2 * n_single_amplitudes:] - - # Initialize operator - generator = FermionOperator() - - # Generate excitations - spin_index_functions = [up_index, down_index] - # Generate all spin-conserving single and double excitations derived - # from one spatial occupied-virtual pair - for i, (p, q) in enumerate( - itertools.product(range(n_virtual), range(n_occupied))): - - # Get indices of spatial orbitals - virtual_spatial = n_occupied + p - occupied_spatial = q - - for spin in range(2): - # Get the functions which map a spatial orbital index to a - # spin orbital index - this_index = spin_index_functions[spin] - other_index = spin_index_functions[1 - spin] - - # Get indices of spin orbitals - virtual_this = this_index(virtual_spatial) - virtual_other = other_index(virtual_spatial) - occupied_this = this_index(occupied_spatial) - occupied_other = other_index(occupied_spatial) - - # Generate single excitations - coeff = t1[i] - generator += FermionOperator(( - (virtual_this, 1), - (occupied_this, 0)), - coeff) - if anti_hermitian: - generator += FermionOperator(( - (occupied_this, 1), - (virtual_this, 0)), - -coeff) - - # Generate double excitation - coeff = t2_1[i] - generator += FermionOperator(( - (virtual_this, 1), - (occupied_this, 0), - (virtual_other, 1), - (occupied_other, 0)), - coeff) - if anti_hermitian: - generator += FermionOperator(( - (occupied_other, 1), - (virtual_other, 0), - (occupied_this, 1), - (virtual_this, 0)), - -coeff) - - # Generate all spin-conserving double excitations derived - # from two spatial occupied-virtual pairs - for i, ((p, q), (r, s)) in enumerate( - itertools.combinations( - itertools.product(range(n_virtual), range(n_occupied)), - 2)): - - # Get indices of spatial orbitals - virtual_spatial_1 = n_occupied + p - occupied_spatial_1 = q - virtual_spatial_2 = n_occupied + r - occupied_spatial_2 = s - - # Generate double excitations - coeff = t2_2[i] - for (spin_a, spin_b) in itertools.product(range(2), repeat=2): - # Get the functions which map a spatial orbital index to a - # spin orbital index - index_a = spin_index_functions[spin_a] - index_b = spin_index_functions[spin_b] - - # Get indices of spin orbitals - virtual_1_a = index_a(virtual_spatial_1) - occupied_1_a = index_a(occupied_spatial_1) - virtual_2_b = index_b(virtual_spatial_2) - occupied_2_b = index_b(occupied_spatial_2) - - if virtual_1_a == virtual_2_b: - continue - if occupied_1_a == occupied_2_b: - continue - else: - - generator += FermionOperator(( - (virtual_1_a, 1), - (occupied_1_a, 0), - (virtual_2_b, 1), - (occupied_2_b, 0)), - coeff) - if anti_hermitian: - generator += FermionOperator(( - (occupied_2_b, 1), - (virtual_2_b, 0), - (occupied_1_a, 1), - (virtual_1_a, 0)), - -coeff) - - return generator diff --git a/qsdk/toolboxes/ansatz_generator/uccsd.py b/qsdk/toolboxes/ansatz_generator/uccsd.py index b29341e15..d65bc18e2 100644 --- a/qsdk/toolboxes/ansatz_generator/uccsd.py +++ b/qsdk/toolboxes/ansatz_generator/uccsd.py @@ -31,12 +31,12 @@ import itertools import numpy as np from pyscf import mp +from openfermion.circuits import uccsd_singlet_generator from qsdk.backendbuddy import Circuit from .ansatz import Ansatz from .ansatz_utils import pauliword_to_circuit -from ._unitary_cc import uccsd_singlet_generator from ._unitary_cc_openshell import uccsd_openshell_paramsize, uccsd_openshell_generator from qsdk.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping from qsdk.toolboxes.qubit_mappings.statevector_mapping import get_reference_circuit diff --git a/qsdk/toolboxes/qubit_mappings/bravyi_kitaev.py b/qsdk/toolboxes/qubit_mappings/bravyi_kitaev.py index 0388925cc..632c8cd84 100644 --- a/qsdk/toolboxes/qubit_mappings/bravyi_kitaev.py +++ b/qsdk/toolboxes/qubit_mappings/bravyi_kitaev.py @@ -23,7 +23,6 @@ from openfermion.utils import count_qubits from openfermion.transforms import bravyi_kitaev as openfermion_bravyi_kitaev -from qsdk.toolboxes.operators import QubitOperator def bravyi_kitaev(fermion_operator, n_qubits): @@ -51,7 +50,4 @@ def bravyi_kitaev(fermion_operator, n_qubits): qubit_operator = openfermion_bravyi_kitaev(fermion_operator, n_qubits=n_qubits) - converted_qubit_op = QubitOperator() - converted_qubit_op.terms = qubit_operator.terms.copy() - - return converted_qubit_op + return qubit_operator diff --git a/qsdk/toolboxes/qubit_mappings/jordan_wigner.py b/qsdk/toolboxes/qubit_mappings/jordan_wigner.py index 6b9ecea2d..d94b6dda7 100644 --- a/qsdk/toolboxes/qubit_mappings/jordan_wigner.py +++ b/qsdk/toolboxes/qubit_mappings/jordan_wigner.py @@ -26,18 +26,9 @@ """Jordan-Wigner transform on fermionic operators.""" - -import itertools -import numpy - -from openfermion.utils import count_qubits -from openfermion.ops import InteractionOperator, MajoranaOperator, DiagonalCoulombHamiltonian from openfermion.transforms import jordan_wigner as openfermion_jordan_wigner -from qsdk.toolboxes.operators import FermionOperator, QubitOperator - - def jordan_wigner(operator): r"""Apply the Jordan-Wigner transform to a FermionOperator, InteractionOperator, or DiagonalCoulombHamiltonian to convert to a @@ -58,281 +49,6 @@ def jordan_wigner(operator): TypeError: Operator must be a FermionOperator, DiagonalCoulombHamiltonian, or InteractionOperator. """ - # return openfermion_jordan_wigner(operator) - if isinstance(operator, FermionOperator): - return jordan_wigner_fermion_operator(operator) - if isinstance(operator, MajoranaOperator): - return jordan_wigner_majorana_operator(operator) - if isinstance(operator, DiagonalCoulombHamiltonian): - return jordan_wigner_diagonal_coulomb_hamiltonian(operator) - if isinstance(operator, InteractionOperator): - return jordan_wigner_interaction_op(operator) - raise TypeError("Operator must be a FermionOperator,MajoranaOperator, " - "DiagonalCoulombHamiltonian, or InteractionOperator.") - - -def jordan_wigner_fermion_operator(operator): - transformed_operator = QubitOperator() - for term in operator.terms: - # Initialize identity matrix. - transformed_term = QubitOperator((), operator.terms[term]) - # Loop through operators, transform and multiply. - for ladder_operator in term: - z_factors = tuple((index, "Z") for - index in range(ladder_operator[0])) - pauli_x_component = QubitOperator( - z_factors + ((ladder_operator[0], "X"),), 0.5) - if ladder_operator[1]: - pauli_y_component = QubitOperator( - z_factors + ((ladder_operator[0], "Y"),), -0.5j) - else: - pauli_y_component = QubitOperator( - z_factors + ((ladder_operator[0], "Y"),), 0.5j) - transformed_term *= pauli_x_component + pauli_y_component - transformed_operator += transformed_term - return transformed_operator - - -def jordan_wigner_majorana_operator(operator): - transformed_operator = QubitOperator() - for term, coeff in operator.terms.items(): - transformed_term = QubitOperator((), coeff) - for majorana_index in term: - q, b = divmod(majorana_index, 2) - z_string = tuple((i, "Z") for i in range(q)) - bit_flip_op = "Y" if b else "X" - transformed_term *= QubitOperator(z_string + ((q, bit_flip_op),)) - transformed_operator += transformed_term - return transformed_operator - - -def jordan_wigner_diagonal_coulomb_hamiltonian(operator): - n_qubits = count_qubits(operator) - qubit_operator = QubitOperator((), operator.constant) - - # Transform diagonal one-body terms - for p in range(n_qubits): - coefficient = operator.one_body[p, p] + operator.two_body[p, p] - qubit_operator += QubitOperator(((p, "Z"),), -.5 * coefficient) - qubit_operator += QubitOperator((), .5 * coefficient) - - # Transform other one-body terms and two-body terms - for p, q in itertools.combinations(range(n_qubits), 2): - # One-body - real_part = numpy.real(operator.one_body[p, q]) - imag_part = numpy.imag(operator.one_body[p, q]) - parity_string = [(i, "Z") for i in range(p + 1, q)] - qubit_operator += QubitOperator( - [(p, "X")] + parity_string + [(q, "X")], .5 * real_part) - qubit_operator += QubitOperator( - [(p, "Y")] + parity_string + [(q, "Y")], .5 * real_part) - qubit_operator += QubitOperator( - [(p, "Y")] + parity_string + [(q, "X")], .5 * imag_part) - qubit_operator += QubitOperator( - [(p, "X")] + parity_string + [(q, "Y")], -.5 * imag_part) - - # Two-body - coefficient = operator.two_body[p, q] - qubit_operator += QubitOperator(((p, "Z"), (q, "Z")), .5 * coefficient) - qubit_operator += QubitOperator((p, "Z"), -.5 * coefficient) - qubit_operator += QubitOperator((q, "Z"), -.5 * coefficient) - qubit_operator += QubitOperator((), .5 * coefficient) - - return qubit_operator - - -def jordan_wigner_interaction_op(iop, n_qubits=None): - """Output InteractionOperator as QubitOperator class under JW transform. - - One could accomplish this very easily by first mapping to fermions and then - mapping to qubits. We skip the middle step for the sake of speed. - - This only works for real InteractionOperators (no complex numbers). - - Returns: - QubitOperator: An instance of the QubitOperator class. - """ - if n_qubits is None: - n_qubits = count_qubits(iop) - if n_qubits < count_qubits(iop): - raise ValueError("Invalid number of qubits specified.") - - # Initialize qubit operator as constant. - qubit_operator = QubitOperator((), iop.constant) - - # Transform diagonal one-body terms - for p in range(n_qubits): - coefficient = iop[(p, 1), (p, 0)] - qubit_operator += jordan_wigner_one_body(p, p, coefficient) - - # Transform other one-body terms and "diagonal" two-body terms - for p, q in itertools.combinations(range(n_qubits), 2): - # One-body - coefficient = .5 * (iop[(p, 1), (q, 0)] + iop[(q, 1), (p, 0)].conjugate()) - qubit_operator += jordan_wigner_one_body(p, q, coefficient) - - # Two-body - coefficient = (iop[(p, 1), (q, 1), (p, 0), (q, 0)] - - iop[(p, 1), (q, 1), (q, 0), (p, 0)] - - iop[(q, 1), (p, 1), (p, 0), (q, 0)] + - iop[(q, 1), (p, 1), (q, 0), (p, 0)]) - qubit_operator += jordan_wigner_two_body(p, q, p, q, coefficient) - - # Transform the rest of the two-body terms - for (p, q), (r, s) in itertools.combinations(itertools.combinations(range(n_qubits), 2), 2): - coefficient = 0.5 * ( - iop[(p, 1), (q, 1), (r, 0), (s, 0)] + - iop[(s, 1), (r, 1), (q, 0), (p, 0)].conjugate() - - iop[(p, 1), (q, 1), (s, 0), (r, 0)] - - iop[(r, 1), (s, 1), (q, 0), (p, 0)].conjugate() - - iop[(q, 1), (p, 1), (r, 0), (s, 0)] - - iop[(s, 1), (r, 1), (p, 0), (q, 0)].conjugate() + - iop[(q, 1), (p, 1), (s, 0), (r, 0)] + - iop[(r, 1), (s, 1), (p, 0), (q, 0)].conjugate()) - qubit_operator += jordan_wigner_two_body(p, q, r, s, coefficient) - - return qubit_operator - - -def jordan_wigner_one_body(p, q, coefficient=1.): - r"""Map the term a^\dagger_p a_q + h.c. to QubitOperator. - - Note that the diagonal terms are divided by a factor of 2 - because they are equal to their own Hermitian conjugate. - """ - # Handle off-diagonal terms. - qubit_operator = QubitOperator() - if p != q: - if p > q: - p, q = q, p - coefficient = coefficient.conjugate() - parity_string = tuple((z, "Z") for z in range(p + 1, q)) - for c, (op_a, op_b) in [(coefficient.real, "XX"), - (coefficient.real, "YY"), - (coefficient.imag, "YX"), - (-coefficient.imag, "XY")]: - operators = ((p, op_a),) + parity_string + ((q, op_b),) - qubit_operator += QubitOperator(operators, .5 * c) - - # Handle diagonal terms. - else: - qubit_operator += QubitOperator((), .5 * coefficient) - qubit_operator += QubitOperator(((p, "Z"),), -.5 * coefficient) - - return qubit_operator - - -def jordan_wigner_two_body(p, q, r, s, coefficient=1.): - r"""Map the term a^\dagger_p a^\dagger_q a_r a_s + h.c. to QubitOperator. - - Note that the diagonal terms are divided by a factor of two because they are - equal to their own Hermitian conjugate. - """ - # Initialize qubit operator. - qubit_operator = QubitOperator() - - # Return zero terms. - if (p == q) or (r == s): - return qubit_operator - - # Handle case of four unique indices. - elif len(set([p, q, r, s])) == 4: - if (p > q) ^ (r > s): - coefficient *= -1 - # Loop through different operators which act on each tensor factor. - for ops in itertools.product("XY", repeat=4): - # Get coefficients. - if ops.count("X") % 2: - coeff = .125 * coefficient.imag - if "".join(ops) in ["XYXX", "YXXX", "YYXY", "YYYX"]: - coeff *= -1 - else: - coeff = .125 * coefficient.real - if "".join(ops) not in ["XXYY", "YYXX"]: - coeff *= -1 - if not coeff: - continue - - # Sort operators. - [(a, operator_a), (b, operator_b), - (c, operator_c), (d, operator_d)] = sorted(zip([p, q, r, s], ops)) - - # Compute operator strings. - operators = ((a, operator_a),) - operators += tuple((z, "Z") for z in range(a + 1, b)) - operators += ((b, operator_b),) - operators += ((c, operator_c),) - operators += tuple((z, "Z") for z in range(c + 1, d)) - operators += ((d, operator_d),) - - # Add term. - qubit_operator += QubitOperator(operators, coeff) - - # Handle case of three unique indices. - elif len(set([p, q, r, s])) == 3: - - # Identify equal tensor factors. - if p == r: - if q > s: - a, b = s, q - coefficient = -coefficient.conjugate() - else: - a, b = q, s - coefficient = -coefficient - c = p - elif p == s: - if q > r: - a, b = r, q - coefficient = coefficient.conjugate() - else: - a, b = q, r - c = p - elif q == r: - if p > s: - a, b = s, p - coefficient = coefficient.conjugate() - else: - a, b = p, s - c = q - elif q == s: - if p > r: - a, b = r, p - coefficient = -coefficient.conjugate() - else: - a, b = p, r - coefficient = -coefficient - c = q - - # Get operators. - parity_string = tuple((z, "Z") for z in range(a + 1, b)) - pauli_z = QubitOperator(((c, "Z"),)) - for c, (op_a, op_b) in [(coefficient.real, "XX"), - (coefficient.real, "YY"), - (coefficient.imag, "YX"), - (-coefficient.imag, "XY")]: - operators = ((a, op_a),) + parity_string + ((b, op_b),) - if not c: - continue - - # Add term. - hopping_term = QubitOperator(operators, c / 4) - qubit_operator -= pauli_z * hopping_term - qubit_operator += hopping_term - - # Handle case of two unique indices. - elif len(set([p, q, r, s])) == 2: - - # Get coefficient. - if p == s: - coeff = -.25 * coefficient - else: - coeff = .25 * coefficient - - # Add terms. - qubit_operator -= QubitOperator((), coeff) - qubit_operator += QubitOperator(((p, "Z"),), coeff) - qubit_operator += QubitOperator(((q, "Z"),), coeff) - qubit_operator -= QubitOperator(((min(q, p), "Z"), (max(q, p), "Z")), - coeff) + qubit_operator = openfermion_jordan_wigner(operator) return qubit_operator diff --git a/qsdk/toolboxes/qubit_mappings/mapping_transform.py b/qsdk/toolboxes/qubit_mappings/mapping_transform.py index 6a566246a..4c43ffdb7 100644 --- a/qsdk/toolboxes/qubit_mappings/mapping_transform.py +++ b/qsdk/toolboxes/qubit_mappings/mapping_transform.py @@ -22,8 +22,9 @@ import numpy as np from collections.abc import Iterable +from openfermion import FermionOperator as ofFermionOperator -from qsdk.toolboxes.operators import FermionOperator +from qsdk.toolboxes.operators import FermionOperator, QubitOperator from qsdk.toolboxes.qubit_mappings import jordan_wigner, bravyi_kitaev, symmetry_conserving_bravyi_kitaev available_mappings = {"JW", "BK", "SCBK"} @@ -94,7 +95,7 @@ def fermion_to_qubit_mapping(fermion_operator, mapping, n_spinorbitals=None, n_e QubitOperator: input operator, encoded in the qubit space. """ # some methods may pass another operator class type. If this is the case, cast to FermionOperator where possible - if not isinstance(fermion_operator, FermionOperator): + if not isinstance(fermion_operator, ofFermionOperator): fermion_operator = get_fermion_operator(fermion_operator) if mapping.upper() not in available_mappings: @@ -122,7 +123,10 @@ def fermion_to_qubit_mapping(fermion_operator, mapping, n_spinorbitals=None, n_e n_electrons=n_electrons, up_then_down=up_then_down) - return qubit_operator + converted_qubit_op = QubitOperator() + converted_qubit_op.terms = qubit_operator.terms.copy() + + return converted_qubit_op def make_up_then_down(fermion_operator, n_spinorbitals): @@ -136,7 +140,7 @@ def make_up_then_down(fermion_operator, n_spinorbitals): Returns: FermionOperator: operator with all spin up followed by all spin down. """ - if not isinstance(fermion_operator, FermionOperator): + if not isinstance(fermion_operator, ofFermionOperator): raise TypeError("Invalid operator input. Must be FermionOperator.") if n_spinorbitals % 2 != 0: raise ValueError("Invalid number of spin-orbitals. Expecting even number.") diff --git a/qsdk/toolboxes/qubit_mappings/symmetry_conserving_bravyi_kitaev.py b/qsdk/toolboxes/qubit_mappings/symmetry_conserving_bravyi_kitaev.py index 3dcad91ab..cc01ffa9d 100644 --- a/qsdk/toolboxes/qubit_mappings/symmetry_conserving_bravyi_kitaev.py +++ b/qsdk/toolboxes/qubit_mappings/symmetry_conserving_bravyi_kitaev.py @@ -33,7 +33,7 @@ import numpy as np import copy -from qsdk.toolboxes.operators import FermionOperator, QubitOperator +from openfermion import FermionOperator as ofFermionOperator from openfermion.transforms import bravyi_kitaev_tree, reorder from openfermion.utils import count_qubits from openfermion.utils import up_then_down as up_then_down_order @@ -78,9 +78,9 @@ def symmetry_conserving_bravyi_kitaev(fermion_operator, n_spinorbitals, Hamiltonian only for the lowest energy even and odd fermion number states, not states with an arbitrary number of fermions. """ - if not isinstance(fermion_operator, FermionOperator): + if not isinstance(fermion_operator, ofFermionOperator): raise ValueError("Supplied operator should be an instance " - "of FermionOperator class") + "of openfermion FermionOperator class.") if type(n_spinorbitals) is not int: raise ValueError("Number of spin-orbitals should be an integer.") if type(n_electrons) is not int: @@ -123,10 +123,7 @@ def symmetry_conserving_bravyi_kitaev(fermion_operator, n_spinorbitals, to_prune = (n_spinorbitals//2 - 1, n_spinorbitals - 1) qubit_operator = prune_unused_indices(qubit_operator, prune_indices=to_prune, n_qubits=n_spinorbitals) - converted_qubit_op = QubitOperator() - converted_qubit_op.terms = qubit_operator.terms.copy() - - return converted_qubit_op + return qubit_operator def edit_operator_for_spin(qubit_operator, spin_orbital, orbital_parity): From 8a171d10a1a5481b6cff3d67b78f9bf4f1cc9872 Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Tue, 16 Nov 2021 11:54:43 -0800 Subject: [PATCH 09/68] Update issue templates Template for bugs and feature requests --- .github/ISSUE_TEMPLATE/bug_report.md | 33 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 24 +++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..2d685b442 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,33 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG]" +labels: bug +assignees: AlexandreF-1qbit, JamesB-1qbit, ValentinS4t1qbit + +--- + +# Issue: Bug Report + +New features should be issued using the `New Feature` template. Please post your general questions on [stackexchange](https://quantumcomputing.stackexchange.com/) and put the tag `tangelo` for visibility. + +**Expected Behavior** +Tell us what should happen. + +**Current Behavior** +Tell us what happens instead of the expected behavior. Try to be clear and concise if you can. Feel free to add screenshots. + +**Steps to Reproduce (minimal example)** +Provide a link to a live example, or an unambiguous set of steps to reproduce this bug. Include code to reproduce, if relevant. +1. Step 1 +2. Step 2 +3. ... + +**Environment** +Please provide at least information about your OS, as well as the branch and version of Tangelo you are using. List other packages used and their version if relevant. + +**Possible Solution** +(optional) Suggest a fix/reason for the bug. + +**Summary** +(optional) Provide a general summary of the bug. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..869f01c18 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,24 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "[FEATURE REQUEST]" +labels: enhancement +assignees: AlexandreF-1qbit, JamesB-1qbit, ValentinS4t1qbit + +--- + +# Issue: Feature Request + +Bugs should be issued using the `Bug Report` template. Please post your general questions on [stackexchange](https://quantumcomputing.stackexchange.com/) and put the tag `tangelo` for visibility reasons. + +**What problem does this feature request help you overcome? Please describe.** +A clear and concise description of what the problem is. Ex: I'm always frustrated when [...] + +**Describe the solution you'd like** +What should it take as input and output ? What kind of interface seems satisfying ? + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From 837675690e8280590604941b4e6061c2636f2f4b Mon Sep 17 00:00:00 2001 From: JamesB-1qbit <84878946+JamesB-1qbit@users.noreply.github.com> Date: Tue, 16 Nov 2021 15:00:11 -0500 Subject: [PATCH 10/68] Reconfigure of qulacs simulation call. (#82) * changed qsimulator to use qulacs backend for sampling qulacs noiseless simulation * specified cirq version compatible with openfermion --- .../github_actions_automated_testing.yml | 2 +- qsdk/backendbuddy/simulator.py | 30 +++++++++++-------- .../measurements/tests/test_measurements.py | 11 ++++--- setup.py | 2 +- 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/.github/workflows/github_actions_automated_testing.yml b/.github/workflows/github_actions_automated_testing.yml index 35b58b7c9..472d52e88 100755 --- a/.github/workflows/github_actions_automated_testing.yml +++ b/.github/workflows/github_actions_automated_testing.yml @@ -30,7 +30,7 @@ jobs: pip install qiskit pip install qulacs pip install amazon-braket-sdk - pip install cirq + pip install cirq==0.12.0 pip install projectq - name: Install Microsoft qsharp/qdk diff --git a/qsdk/backendbuddy/simulator.py b/qsdk/backendbuddy/simulator.py index a0338076d..a0c50af4f 100644 --- a/qsdk/backendbuddy/simulator.py +++ b/qsdk/backendbuddy/simulator.py @@ -142,26 +142,30 @@ def simulate(self, source_circuit, return_statevector=False, initial_statevector if initial_statevector is not None: state.load(initial_statevector) - samples = list() - shots = self.n_shots if (source_circuit.is_mixed_state or self._noise_model) else 1 - for i in range(shots): - - translated_circuit.update_quantum_state(state) - if source_circuit.is_mixed_state or self._noise_model: + if (source_circuit.is_mixed_state or self._noise_model): + samples = list() + for i in range(self.n_shots): + translated_circuit.update_quantum_state(state) samples.append(state.sampling(1)[0]) - if initial_statevector: + if initial_statevector is not None: state.load(initial_statevector) else: state.set_zero_state() - else: - self._current_state = state - python_statevector = state.get_vector() - frequencies = self._statevector_to_frequencies(python_statevector) - return (frequencies, np.array(python_statevector)) if return_statevector else (frequencies, None) + python_statevector = None + elif self.n_shots is not None: + translated_circuit.update_quantum_state(state) + python_statevector = np.array(state.get_vector()) if return_statevector else None + samples = state.sampling(self.n_shots) + else: + translated_circuit.update_quantum_state(state) + self._current_state = state + python_statevector = state.get_vector() + frequencies = self._statevector_to_frequencies(python_statevector) + return (frequencies, np.array(python_statevector)) if return_statevector else (frequencies, None) frequencies = {self.__int_to_binstr(k, source_circuit.width): v / self.n_shots for k, v in Counter(samples).items()} - return (frequencies, None) + return (frequencies, python_statevector) elif self._target == "qiskit": import qiskit diff --git a/qsdk/toolboxes/measurements/tests/test_measurements.py b/qsdk/toolboxes/measurements/tests/test_measurements.py index 9210b9b35..e817e978a 100644 --- a/qsdk/toolboxes/measurements/tests/test_measurements.py +++ b/qsdk/toolboxes/measurements/tests/test_measurements.py @@ -98,10 +98,13 @@ def test_measurement_uniform_H2(self): exp_val = 0. sim = Simulator(target=default_simulator, n_shots=1) for m_basis, coef in qb_ham.terms.items(): - term_circ = abs_circ + Circuit(measurement_basis_gates(m_basis)) - sim.n_shots = mes_dict[m_basis] - freqs, _ = sim.simulate(term_circ) - exp_val += sim.get_expectation_value_from_frequencies_oneterm(m_basis, freqs) * coef + if m_basis: + term_circ = abs_circ + Circuit(measurement_basis_gates(m_basis)) + sim.n_shots = mes_dict[m_basis] + freqs, _ = sim.simulate(term_circ) + exp_val += sim.get_expectation_value_from_frequencies_oneterm(m_basis, freqs) * coef + else: + exp_val += coef diffs.append(abs(exp_val - exp_val_exact)) # Check that on average, we deliver the expected accuracy diff --git a/setup.py b/setup.py index 9858df848..f1876a3b4 100755 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ def install(package): install('wheel') install('h5py==3.2.0') -install('pyscf==1.7.6.post1') +install('pyscf==1.7.6') setuptools.setup( name="qSDK", From 8da3f5f81b6a1e758a6ba41dfa24728522e92ea4 Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Tue, 16 Nov 2021 12:02:01 -0800 Subject: [PATCH 11/68] Update bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 2d685b442..8fb671bd1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,7 +1,7 @@ --- name: Bug report about: Create a report to help us improve -title: "[BUG]" +title: "[BUG] " labels: bug assignees: AlexandreF-1qbit, JamesB-1qbit, ValentinS4t1qbit From ccc8473f3a4d74338bc48e3e74c81bf34d3871e1 Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Tue, 16 Nov 2021 12:02:23 -0800 Subject: [PATCH 12/68] Update feature_request.md --- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 869f01c18..d0437ecc9 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,7 +1,7 @@ --- name: Feature request about: Suggest an idea for this project -title: "[FEATURE REQUEST]" +title: "[FEATURE REQUEST] " labels: enhancement assignees: AlexandreF-1qbit, JamesB-1qbit, ValentinS4t1qbit From 53b8b229950f24c3e1e5bb994363509237de9462 Mon Sep 17 00:00:00 2001 From: AlexandreF-1qbit <76115575+AlexandreF-1qbit@users.noreply.github.com> Date: Tue, 16 Nov 2021 18:09:03 -0500 Subject: [PATCH 13/68] Fix random failing tests (#89) * H4 tests -> H4 2+. Might be more robust. --- .../variational/tests/test_vqe_solver.py | 23 +---------- .../tests/dmet/test_dmet.py | 15 +++++-- .../ansatz_generator/tests/test_hea.py | 12 +++--- .../ansatz_generator/tests/test_uccsd.py | 18 ++++----- .../ansatz_generator/tests/test_upccgsd.py | 27 ++++++------- qsdk/toolboxes/molecular_computation/rdms.py | 39 +++++++++++++++++++ 6 files changed, 79 insertions(+), 55 deletions(-) create mode 100644 qsdk/toolboxes/molecular_computation/rdms.py diff --git a/qsdk/algorithms/variational/tests/test_vqe_solver.py b/qsdk/algorithms/variational/tests/test_vqe_solver.py index 345664d75..2d597d400 100644 --- a/qsdk/algorithms/variational/tests/test_vqe_solver.py +++ b/qsdk/algorithms/variational/tests/test_vqe_solver.py @@ -20,28 +20,7 @@ from qsdk.molecule_library import mol_H2_sto3g, mol_H4_sto3g, mol_H4_cation_sto3g, mol_NaH_sto3g, mol_NaH_sto3g from qsdk.toolboxes.ansatz_generator.uccsd import UCCSD from qsdk.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping - - -def matricize_2rdm(two_rdm, n_orbitals): - """Turns the two_rdm tensor into a matrix for test purposes.""" - - l = 0 - sq = n_orbitals * n_orbitals - jpqrs = np.zeros((n_orbitals, n_orbitals), dtype=int) - for i in range(n_orbitals): - for j in range(n_orbitals): - jpqrs[i, j] = l - l += 1 - - rho = np.zeros((sq, sq), dtype=complex) - for i in range(n_orbitals): - for j in range(n_orbitals): - ij = jpqrs[i, j] - for k in range(n_orbitals): - for l in range(n_orbitals): - kl = jpqrs[k, l] - rho[ij, kl] += two_rdm[i, k, j, l] - return rho +from qsdk.toolboxes.molecular_computation.rdms import matricize_2rdm class VQESolverTest(unittest.TestCase): diff --git a/qsdk/problem_decomposition/tests/dmet/test_dmet.py b/qsdk/problem_decomposition/tests/dmet/test_dmet.py index eaa08537a..947052b67 100644 --- a/qsdk/problem_decomposition/tests/dmet/test_dmet.py +++ b/qsdk/problem_decomposition/tests/dmet/test_dmet.py @@ -19,6 +19,8 @@ from qsdk.problem_decomposition import dmet from qsdk.problem_decomposition.dmet.dmet_problem_decomposition import Localization, DMETProblemDecomposition from qsdk.algorithms.variational import VQESolver +from qsdk.toolboxes.molecular_computation.rdms import matricize_2rdm + class DMETProblemDecompositionTest(unittest.TestCase): @@ -211,7 +213,7 @@ def test_retrieving_quantum_data(self): [[ 2.53371393e-07, -2.03773912e-05], [-2.03773912e-05, 1.63885145e-03]]]] - fragment, q_H, q_circuit = dmet_solver.quantum_fragments_data[0] + fragment, _, q_circuit = dmet_solver.quantum_fragments_data[0] vqe_solver = VQESolver({"molecule": fragment, "ansatz": q_circuit, "qubit_mapping": "scBK"}) @@ -220,8 +222,15 @@ def test_retrieving_quantum_data(self): onerdm, twordm = vqe_solver.get_rdm(vqe_solver.optimal_var_params) - np.testing.assert_array_almost_equal(ref_onerdm, onerdm) - np.testing.assert_array_almost_equal(ref_twordm, twordm) + # Test traces of matrices + n_elec = fragment.n_active_electrons + n_orb = fragment.n_active_sos // 2 + self.assertAlmostEqual(np.trace(onerdm), n_elec, delta=1e-3, + msg="Trace of one_rdm does not match number of electrons") + + rho = matricize_2rdm(twordm, n_orb) + self.assertAlmostEqual(np.trace(rho), n_elec * (n_elec - 1), delta=1e-3, + msg="Trace of two_rdm does not match n_elec * (n_elec-1)") if __name__ == "__main__": diff --git a/qsdk/toolboxes/ansatz_generator/tests/test_hea.py b/qsdk/toolboxes/ansatz_generator/tests/test_hea.py index d2a9ebd31..58f8202d5 100644 --- a/qsdk/toolboxes/ansatz_generator/tests/test_hea.py +++ b/qsdk/toolboxes/ansatz_generator/tests/test_hea.py @@ -15,7 +15,7 @@ import unittest import numpy as np -from qsdk.molecule_library import mol_H2_sto3g, mol_H4_sto3g +from qsdk.molecule_library import mol_H2_sto3g, mol_H4_doublecation_minao from qsdk.toolboxes.qubit_mappings import jordan_wigner from qsdk.toolboxes.ansatz_generator.hea import HEA @@ -77,8 +77,8 @@ def test_hea_H2(self): energy = sim.get_expectation_value(qubit_hamiltonian, hea_ansatz.circuit) self.assertAlmostEqual(energy, -1.1372661564779496, delta=1e-6) - def test_hea_H4(self): - """Verify closed-shell HEA functionalities for H4.""" + def test_hea_H4_doublecation(self): + """Verify closed-shell HEA functionalities for H4 2+.""" var_params = [-2.34142720e-04, 6.28472420e+00, 4.67668267e+00, -3.14063369e+00, -1.53697174e+00, -6.22546556e+00, -3.11351342e+00, 3.14158366e+00, @@ -88,16 +88,16 @@ def test_hea_H4(self): -1.57734737e+00, -3.63191375e+00, -3.15706332e+00, -1.73625091e+00] # Build circuit with Ry rotationas instead of RZ*RX*RZ rotations - hea_ansatz = HEA(molecule=mol_H4_sto3g, rot_type="real") + hea_ansatz = HEA(molecule=mol_H4_doublecation_minao, rot_type="real") hea_ansatz.build_circuit() # Build qubit hamiltonian for energy evaluation - qubit_hamiltonian = jordan_wigner(mol_H4_sto3g.fermionic_hamiltonian) + qubit_hamiltonian = jordan_wigner(mol_H4_doublecation_minao.fermionic_hamiltonian) # Assert energy returned is as expected for given parameters hea_ansatz.update_var_params(var_params) energy = sim.get_expectation_value(qubit_hamiltonian, hea_ansatz.circuit) - self.assertAlmostEqual(energy, -0.6534450968231997, delta=1e-6) + self.assertAlmostEqual(energy, -0.795317, delta=1e-4) if __name__ == "__main__": diff --git a/qsdk/toolboxes/ansatz_generator/tests/test_uccsd.py b/qsdk/toolboxes/ansatz_generator/tests/test_uccsd.py index 086e3eb9a..44ee09de1 100644 --- a/qsdk/toolboxes/ansatz_generator/tests/test_uccsd.py +++ b/qsdk/toolboxes/ansatz_generator/tests/test_uccsd.py @@ -15,7 +15,7 @@ import unittest import numpy as np -from qsdk.molecule_library import mol_H2_sto3g, mol_H4_sto3g, mol_H4_cation_sto3g +from qsdk.molecule_library import mol_H2_sto3g, mol_H4_sto3g, mol_H4_doublecation_minao, mol_H4_cation_sto3g from qsdk.toolboxes.qubit_mappings import jordan_wigner from qsdk.toolboxes.ansatz_generator.uccsd import UCCSD @@ -122,25 +122,25 @@ def test_uccsd_H4_open(self): energy = sim.get_expectation_value(qubit_hamiltonian, uccsd_ansatz.circuit) self.assertAlmostEqual(energy, -1.639461490, delta=1e-6) - def test_uccsd_H4(self): - """Verify closed-shell UCCSD functionalities for H4.""" + def test_uccsd_H4_doublecation(self): + """Verify closed-shell UCCSD functionalities for H4 2+.""" - var_params = [-3.96898484e-04, 4.59786847e-05, 3.95285013e-05, 1.85885610e-04, 1.05759154e-02, - 3.47363359e-01, 3.42657596e-02, 1.45006203e-02, 7.43941871e-02, 7.57255601e-03, - -1.83407761e-01, -1.03261491e-01, 1.34258277e-02, -3.78096407e-02] + var_params = [-1.32047062e-02, -7.16419743e-06, -9.25426159e-06, + 6.84650642e-02, 6.32462456e-02, 1.44675096e-02, + -8.34820283e-06, -7.79703747e-06, 3.28660359e-02] # Build circuit - uccsd_ansatz = UCCSD(mol_H4_sto3g) + uccsd_ansatz = UCCSD(mol_H4_doublecation_minao) uccsd_ansatz.build_circuit() # Build qubit hamiltonian for energy evaluation - qubit_hamiltonian = jordan_wigner(mol_H4_sto3g.fermionic_hamiltonian) + qubit_hamiltonian = jordan_wigner(mol_H4_doublecation_minao.fermionic_hamiltonian) # Assert energy returned is as expected for given parameters sim = Simulator() uccsd_ansatz.update_var_params(var_params) energy = sim.get_expectation_value(qubit_hamiltonian, uccsd_ansatz.circuit) - self.assertAlmostEqual(energy, -1.9778041, delta=1e-6) + self.assertAlmostEqual(energy, -0.854607, delta=1e-4) if __name__ == "__main__": diff --git a/qsdk/toolboxes/ansatz_generator/tests/test_upccgsd.py b/qsdk/toolboxes/ansatz_generator/tests/test_upccgsd.py index 861ad1f9a..ed4747860 100644 --- a/qsdk/toolboxes/ansatz_generator/tests/test_upccgsd.py +++ b/qsdk/toolboxes/ansatz_generator/tests/test_upccgsd.py @@ -15,7 +15,7 @@ import unittest import numpy as np -from qsdk.molecule_library import mol_H2_sto3g, mol_H4_sto3g, mol_H4_cation_sto3g +from qsdk.molecule_library import mol_H2_sto3g, mol_H4_doublecation_minao, mol_H4_cation_sto3g from qsdk.toolboxes.qubit_mappings import jordan_wigner from qsdk.toolboxes.ansatz_generator.upccgsd import UpCCGSD @@ -91,31 +91,28 @@ def test_upccgsd_H4_open(self): energy = sim.get_expectation_value(qubit_hamiltonian, upccgsd_ansatz.circuit) self.assertAlmostEqual(energy, -1.6412047312, delta=1e-6) - def test_upccgsd_H4(self): - """Verify closed-shell UpCCGSD functionalities for H4.""" + def test_upccgsd_H4_doublecation(self): + """Verify closed-shell UpCCGSD functionalities for H4 2+.""" - var_params = [ 3.79061344e-02, -4.30067212e-02, 3.02230152e-02, 3.42936301e-03, - -6.09234584e-04, -3.14370905e-02, -4.86666676e-02, 5.16834522e-02, - 2.58779710e-03, 2.58848760e-03, 2.50477121e-02, 3.13929977e-02, - 6.60326773e-03, -9.12896032e-02, 1.53572944e-01, 1.87098400e-01, - -4.15148608e-02, 4.92466084e-02, 7.04965743e-01, 7.18739139e-01, - 1.54329908e-02, -3.33233433e-02, -9.08825509e-03, 3.93555394e-02, - 5.47661674e-02, -4.91387503e-02, -1.11946468e-02, -2.61401420e-02, - 5.38915256e-01, 5.37080003e-01, -1.77856374e-02, 1.17461439e-02, - 2.73552144e-02, 6.02186436e-01, -8.58400153e-02, -1.17667425e-01] + var_params = [1.08956248, 1.08956247, 1.05305993, 1.05305993, 0.8799399, 0.8799399, + 0.88616586, 0.88616586, 1.09532143, 1.09532143, 1.23586857, 1.23586857, + 1.09001216, 0.85772769, 1.28020861, 1.05820721, 0.9680792 , 1.01693601, + 0.68355852, 0.68355852, 1.30303827, 1.30303827, 0.74524063, 0.74524063, + 0.36958813, 0.36958813, 1.37092805, 1.37092805, 0.92860293, 0.92860293, + 1.30296676, 0.5803438 , 1.42469953, 1.05666723, 0.86961358, 0.55347531] # Build circuit - upccgsd_ansatz = UpCCGSD(mol_H4_sto3g) + upccgsd_ansatz = UpCCGSD(mol_H4_doublecation_minao) upccgsd_ansatz.build_circuit() # Build qubit hamiltonian for energy evaluation - qubit_hamiltonian = jordan_wigner(mol_H4_sto3g.fermionic_hamiltonian) + qubit_hamiltonian = jordan_wigner(mol_H4_doublecation_minao.fermionic_hamiltonian) # Assert energy returned is as expected for given parameters sim = Simulator() upccgsd_ansatz.update_var_params(var_params) energy = sim.get_expectation_value(qubit_hamiltonian, upccgsd_ansatz.circuit) - self.assertAlmostEqual(energy, -1.968345618, delta=1e-6) + self.assertAlmostEqual(energy, -0.854608, delta=1e-4) if __name__ == "__main__": diff --git a/qsdk/toolboxes/molecular_computation/rdms.py b/qsdk/toolboxes/molecular_computation/rdms.py new file mode 100644 index 000000000..9a56e9fe8 --- /dev/null +++ b/qsdk/toolboxes/molecular_computation/rdms.py @@ -0,0 +1,39 @@ +# Copyright 2021 1QB Information Technologies Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Module containing functions to manipulate 1- and 2-RDMs.""" + +import numpy as np + + +def matricize_2rdm(two_rdm, n_orbitals): + """Turns the two_rdm tensor into a matrix for test purposes.""" + + l = 0 + sq = n_orbitals * n_orbitals + jpqrs = np.zeros((n_orbitals, n_orbitals), dtype=int) + for i in range(n_orbitals): + for j in range(n_orbitals): + jpqrs[i, j] = l + l += 1 + + rho = np.zeros((sq, sq), dtype=complex) + for i in range(n_orbitals): + for j in range(n_orbitals): + ij = jpqrs[i, j] + for k in range(n_orbitals): + for l in range(n_orbitals): + kl = jpqrs[k, l] + rho[ij, kl] += two_rdm[i, k, j, l] + return rho From df42da1626c46e78b3a2f1e3251be968258139c7 Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Tue, 16 Nov 2021 21:11:53 -0800 Subject: [PATCH 14/68] Branding (Tangelo, Good Chemistry Company) (#87) * Name substitution (branding) * Removed deprecated connection methods to QPUs and old files --- .../github_actions_automated_testing.yml | 8 +- Dockerfile | 4 +- LICENSE | 2 +- README.rst | 21 +- dev_tools/build_sphinx_docs.sh | 4 +- docs/source/conf.py | 4 +- docs/source/index.rst | 6 +- examples/adapt.ipynb | 26 +-- examples/backendbuddy/1.the_basics.ipynb | 22 +- .../backendbuddy/3.noisy_simulation.ipynb | 12 +- examples/backendbuddy/VQE_H2.py | 2 +- examples/backendbuddy/custom_ansatz.py | 2 +- .../backendbuddy/import_projectq_circuit.py | 61 ------ examples/backendbuddy/ionq_postprocessing.py | 55 ----- .../qsharp/qsharp_circuit_generation.py | 6 +- .../backendbuddy/qsharp/qsharp_execution.py | 8 +- examples/dmet.ipynb | 30 +-- examples/overview_endtoend.ipynb | 60 +++--- examples/problem_decomposition_oniom.ipynb | 10 +- ...st_cloud_hardware_experiments_braket.ipynb | 22 +- examples/test_notebooks.py | 6 +- examples/vqe.ipynb | 32 +-- examples/vqe_custom_ansatz_hamiltonian.ipynb | 24 +-- .../qpu_connection/honeywell_connection.py | 194 ------------------ .../qpu_connection/ionq_connection.py | 185 ----------------- .../qpu_connection/qpu_connection.py | 51 ----- .../tests/test_connection_honeywell.py | 88 -------- .../tests/test_connection_ionq.py | 101 --------- qsdk/problem_decomposition/oniom/__init__.py | 14 -- .../oniom/_helpers/__init__.py | 14 -- qsdk/problem_decomposition/tests/__init__.py | 14 -- .../tests/dmet/__init__.py | 14 -- .../tests/oniom/__init__.py | 14 -- qsdk/toolboxes/__init__.py | 14 -- qsdk/toolboxes/ansatz_generator/__init__.py | 14 -- .../ansatz_generator/tests/__init__.py | 14 -- qsdk/toolboxes/measurements/tests/__init__.py | 14 -- .../molecular_computation/__init__.py | 14 -- .../molecular_computation/tests/__init__.py | 14 -- qsdk/toolboxes/operators/tests/__init__.py | 14 -- .../post_processing/tests/__init__.py | 14 -- .../qubit_mappings/tests/__init__.py | 14 -- setup.py | 18 +- {qsdk => tangelo}/__init__.py | 4 +- {qsdk => tangelo}/algorithms/__init__.py | 2 +- .../algorithms/classical/__init__.py | 2 +- .../algorithms/classical/ccsd_solver.py | 4 +- .../algorithms/classical/fci_solver.py | 4 +- .../classical/semi_empirical_solver.py | 4 +- .../algorithms/classical/tests}/__init__.py | 2 +- .../classical/tests/test_ccsd_solver.py | 6 +- .../classical/tests/test_fci_solver.py | 6 +- .../tests/test_semi_empirical_solver.py | 6 +- .../algorithms/electronic_structure_solver.py | 2 +- .../algorithms/variational/__init__.py | 2 +- .../variational/adapt_vqe_solver.py | 16 +- .../algorithms/variational}/tests/__init__.py | 2 +- .../tests/test_adapt_vqe_solver.py | 6 +- .../variational/tests/test_vqe_solver.py | 14 +- .../algorithms/variational/vqe_solver.py | 32 +-- {qsdk => tangelo}/backendbuddy/__init__.py | 2 +- {qsdk => tangelo}/backendbuddy/circuit.py | 4 +- {qsdk => tangelo}/backendbuddy/gate.py | 2 +- .../backendbuddy/helpers/__init__.py | 2 +- .../backendbuddy/helpers/circuits/__init__.py | 2 +- .../helpers/circuits/measurement_basis.py | 4 +- .../helpers/operators/__init__.py | 2 +- .../helpers/operators/operators.py | 2 +- .../backendbuddy/noisy_simulation/__init__.py | 2 +- .../noisy_simulation/noise_models.py | 4 +- .../backendbuddy/qdk_template.py | 2 +- .../backendbuddy/qpu_connection/__init__.py | 4 +- .../qpu_connection/qemist_cloud_connection.py | 4 +- {qsdk => tangelo}/backendbuddy/simulator.py | 12 +- .../backendbuddy/tests/__init__.py | 2 +- .../backendbuddy/tests/data/H2_UCCSD.qasm | 0 .../tests/data/H2_qubit_hamiltonian.txt | 0 .../backendbuddy/tests/data/H4_UCCSD.qasm | 0 .../tests/data/H4_qubit_hamiltonian.txt | 0 .../tests/data/projectq_circuit.txt | 0 .../backendbuddy/tests/test_circuits.py | 4 +- .../backendbuddy/tests/test_gates.py | 4 +- .../backendbuddy/tests/test_simulator.py | 8 +- .../tests/test_simulator_noisy.py | 8 +- .../backendbuddy/tests/test_translator.py | 8 +- .../backendbuddy/translator/__init__.py | 4 +- .../backendbuddy/translator/qdk_template.py | 2 +- .../translator/translate_braket.py | 2 +- .../backendbuddy/translator/translate_cirq.py | 2 +- .../translator/translate_json_ionq.py | 2 +- .../translator/translate_openqasm.py | 4 +- .../translator/translate_projectq.py | 4 +- .../backendbuddy/translator/translate_qdk.py | 2 +- .../translator/translate_qiskit.py | 2 +- .../translator/translate_qulacs.py | 2 +- {qsdk => tangelo}/helpers/__init__.py | 4 +- {qsdk => tangelo}/helpers/utils.py | 2 +- {qsdk => tangelo}/molecule_library.py | 4 +- .../problem_decomposition/__init__.py | 2 +- .../problem_decomposition/dmet}/__init__.py | 2 +- .../dmet/_helpers/__init__.py | 2 +- .../dmet/_helpers/dmet_bath.py | 2 +- .../dmet/_helpers/dmet_fragment.py | 2 +- .../dmet/_helpers/dmet_onerdm.py | 2 +- .../dmet/_helpers/dmet_orbitals.py | 2 +- .../dmet/_helpers/dmet_scf.py | 2 +- .../dmet/_helpers/dmet_scf_guess.py | 2 +- .../dmet/dmet_problem_decomposition.py | 14 +- .../problem_decomposition/dmet/fragment.py | 6 +- .../electron_localization/__init__.py | 2 +- .../electron_localization/iao_localization.py | 2 +- .../meta_lowdin_localization.py | 2 +- .../problem_decomposition/oniom/__init__.py | 14 ++ .../oniom/_helpers/__init__.py | 14 ++ .../oniom/_helpers/helper_classes.py | 6 +- .../oniom/oniom_problem_decomposition.py | 6 +- .../problem_decomposition.py | 2 +- .../problem_decomposition/tests/__init__.py | 14 ++ .../tests/dmet/__init__.py | 14 ++ .../data/test_dmet_oneshot_loop_core_rdm.txt | 0 .../data/test_dmet_oneshot_loop_low_rdm.txt | 0 .../tests/dmet/data/test_dmet_orbitals.txt | 0 .../tests/dmet/test_dmet.py | 11 +- .../tests/dmet/test_dmet_fragment.py | 4 +- .../tests/dmet/test_dmet_oneshot_loop.py | 14 +- .../tests/dmet/test_dmet_orbitals.py | 6 +- .../tests/dmet/test_dmet_vqe.py | 6 +- .../tests/oniom/__init__.py | 14 ++ .../tests/oniom/test_oniom.py | 8 +- tangelo/toolboxes/__init__.py | 14 ++ .../toolboxes/ansatz_generator/__init__.py | 14 ++ .../ansatz_generator/_general_unitary_cc.py | 4 +- .../ansatz_generator/_hea_circuit.py | 4 +- .../ansatz_generator/_unitary_cc_openshell.py | 4 +- .../ansatz_generator/_unitary_cc_paired.py | 6 +- .../ansatz_generator/adapt_ansatz.py | 12 +- .../toolboxes/ansatz_generator/ansatz.py | 2 +- .../ansatz_generator/ansatz_utils.py | 4 +- .../ansatz_generator/fermionic_operators.py | 6 +- .../toolboxes/ansatz_generator/hea.py | 8 +- .../ansatz_generator/penalty_terms.py | 6 +- .../toolboxes/ansatz_generator/rucc.py | 4 +- .../ansatz_generator/tests/__init__.py | 14 ++ .../tests/test_adapt_ansatz.py | 8 +- .../tests/test_fermionic_operators.py | 12 +- .../tests/test_general_unitary_cc.py | 4 +- .../ansatz_generator/tests/test_hea.py | 10 +- .../tests/test_penalty_terms.py | 12 +- .../ansatz_generator/tests/test_rucc.py | 4 +- .../ansatz_generator/tests/test_uccsd.py | 11 +- .../ansatz_generator/tests/test_upccgsd.py | 11 +- .../tests/test_variational_circuit.py | 6 +- .../toolboxes/ansatz_generator/uccsd.py | 8 +- .../toolboxes/ansatz_generator/upccgsd.py | 8 +- .../ansatz_generator/variational_circuit.py | 6 +- .../toolboxes/measurements/__init__.py | 2 +- .../measurements/estimate_measurements.py | 2 +- .../measurements/qubit_terms_grouping.py | 4 +- .../toolboxes/measurements/tests/__init__.py | 14 ++ .../toolboxes/measurements/tests/data | 0 .../measurements/tests/test_measurements.py | 12 +- .../tests/test_qubit_terms_grouping.py | 8 +- .../molecular_computation/__init__.py | 14 ++ .../molecular_computation/frozen_orbitals.py | 2 +- .../molecular_computation/molecule.py | 6 +- .../toolboxes/molecular_computation/rdms.py | 0 .../molecular_computation/tests/__init__.py | 14 ++ .../tests/test_frozen_orbitals.py | 6 +- .../tests/test_molecule.py | 8 +- .../toolboxes/operators/__init__.py | 2 +- .../toolboxes/operators/operators.py | 4 +- tangelo/toolboxes/operators/tests/__init__.py | 14 ++ .../operators/tests/test_operators.py | 4 +- .../toolboxes/post_processing/__init__.py | 2 +- .../post_processing/bootstrapping.py | 2 +- .../mc_weeny_rdm_purification.py | 2 +- .../post_processing/tests/__init__.py | 14 ++ .../post_processing/tests/data/rdm1_nw.npy | Bin .../tests/data/rdm1_nw_exact.npy | Bin .../post_processing/tests/data/rdm2_nw.npy | Bin .../tests/data/rdm2_nw_exact.npy | Bin .../tests/data/rdm2_spin_pot7_exact.npy | Bin .../data/rdm2_spin_pot7_experimental.npy | Bin .../tests/test_mcweeny_purification.py | 4 +- .../toolboxes/qubit_mappings/__init__.py | 2 +- .../toolboxes/qubit_mappings/bravyi_kitaev.py | 2 +- .../toolboxes/qubit_mappings/jordan_wigner.py | 2 +- .../qubit_mappings/mapping_transform.py | 6 +- .../qubit_mappings/statevector_mapping.py | 8 +- .../symmetry_conserving_bravyi_kitaev.py | 2 +- .../qubit_mappings/tests/__init__.py | 14 ++ .../tests/test_bravyi_kitaev.py | 14 +- .../tests/test_mapping_transform.py | 8 +- .../qubit_mappings/tests/test_qubitizer.py | 6 +- .../tests/test_statevector_mapping.py | 4 +- 195 files changed, 663 insertions(+), 1416 deletions(-) delete mode 100755 examples/backendbuddy/import_projectq_circuit.py delete mode 100755 examples/backendbuddy/ionq_postprocessing.py delete mode 100644 qsdk/backendbuddy/qpu_connection/honeywell_connection.py delete mode 100644 qsdk/backendbuddy/qpu_connection/ionq_connection.py delete mode 100644 qsdk/backendbuddy/qpu_connection/qpu_connection.py delete mode 100644 qsdk/backendbuddy/tests/test_connection_honeywell.py delete mode 100644 qsdk/backendbuddy/tests/test_connection_ionq.py delete mode 100644 qsdk/problem_decomposition/oniom/__init__.py delete mode 100644 qsdk/problem_decomposition/oniom/_helpers/__init__.py delete mode 100644 qsdk/problem_decomposition/tests/__init__.py delete mode 100644 qsdk/problem_decomposition/tests/dmet/__init__.py delete mode 100644 qsdk/problem_decomposition/tests/oniom/__init__.py delete mode 100644 qsdk/toolboxes/__init__.py delete mode 100644 qsdk/toolboxes/ansatz_generator/__init__.py delete mode 100644 qsdk/toolboxes/ansatz_generator/tests/__init__.py delete mode 100644 qsdk/toolboxes/measurements/tests/__init__.py delete mode 100644 qsdk/toolboxes/molecular_computation/__init__.py delete mode 100644 qsdk/toolboxes/molecular_computation/tests/__init__.py delete mode 100644 qsdk/toolboxes/operators/tests/__init__.py delete mode 100644 qsdk/toolboxes/post_processing/tests/__init__.py delete mode 100644 qsdk/toolboxes/qubit_mappings/tests/__init__.py rename {qsdk => tangelo}/__init__.py (85%) rename {qsdk => tangelo}/algorithms/__init__.py (92%) rename {qsdk => tangelo}/algorithms/classical/__init__.py (92%) rename {qsdk => tangelo}/algorithms/classical/ccsd_solver.py (95%) rename {qsdk => tangelo}/algorithms/classical/fci_solver.py (97%) rename {qsdk => tangelo}/algorithms/classical/semi_empirical_solver.py (95%) rename {qsdk/problem_decomposition/dmet => tangelo/algorithms/classical/tests}/__init__.py (91%) rename {qsdk => tangelo}/algorithms/classical/tests/test_ccsd_solver.py (93%) rename {qsdk => tangelo}/algorithms/classical/tests/test_fci_solver.py (93%) rename {qsdk => tangelo}/algorithms/classical/tests/test_semi_empirical_solver.py (87%) rename {qsdk => tangelo}/algorithms/electronic_structure_solver.py (97%) rename {qsdk => tangelo}/algorithms/variational/__init__.py (92%) rename {qsdk => tangelo}/algorithms/variational/adapt_vqe_solver.py (95%) rename {qsdk/algorithms/classical => tangelo/algorithms/variational}/tests/__init__.py (91%) rename {qsdk => tangelo}/algorithms/variational/tests/test_adapt_vqe_solver.py (93%) rename {qsdk => tangelo}/algorithms/variational/tests/test_vqe_solver.py (97%) rename {qsdk => tangelo}/algorithms/variational/vqe_solver.py (95%) rename {qsdk => tangelo}/backendbuddy/__init__.py (92%) rename {qsdk => tangelo}/backendbuddy/circuit.py (98%) rename {qsdk => tangelo}/backendbuddy/gate.py (98%) rename {qsdk => tangelo}/backendbuddy/helpers/__init__.py (92%) rename {qsdk => tangelo}/backendbuddy/helpers/circuits/__init__.py (91%) rename {qsdk => tangelo}/backendbuddy/helpers/circuits/measurement_basis.py (95%) rename {qsdk => tangelo}/backendbuddy/helpers/operators/__init__.py (91%) rename {qsdk => tangelo}/backendbuddy/helpers/operators/operators.py (97%) rename {qsdk => tangelo}/backendbuddy/noisy_simulation/__init__.py (91%) rename {qsdk => tangelo}/backendbuddy/noisy_simulation/noise_models.py (98%) rename {qsdk => tangelo}/backendbuddy/qdk_template.py (97%) rename {qsdk => tangelo}/backendbuddy/qpu_connection/__init__.py (81%) rename {qsdk => tangelo}/backendbuddy/qpu_connection/qemist_cloud_connection.py (97%) rename {qsdk => tangelo}/backendbuddy/simulator.py (98%) rename {qsdk => tangelo}/backendbuddy/tests/__init__.py (91%) rename {qsdk => tangelo}/backendbuddy/tests/data/H2_UCCSD.qasm (100%) rename {qsdk => tangelo}/backendbuddy/tests/data/H2_qubit_hamiltonian.txt (100%) rename {qsdk => tangelo}/backendbuddy/tests/data/H4_UCCSD.qasm (100%) rename {qsdk => tangelo}/backendbuddy/tests/data/H4_qubit_hamiltonian.txt (100%) rename {qsdk => tangelo}/backendbuddy/tests/data/projectq_circuit.txt (100%) rename {qsdk => tangelo}/backendbuddy/tests/test_circuits.py (97%) rename {qsdk => tangelo}/backendbuddy/tests/test_gates.py (95%) rename {qsdk => tangelo}/backendbuddy/tests/test_simulator.py (98%) rename {qsdk => tangelo}/backendbuddy/tests/test_simulator_noisy.py (97%) rename {qsdk => tangelo}/backendbuddy/tests/test_translator.py (98%) rename {qsdk => tangelo}/backendbuddy/translator/__init__.py (93%) rename {qsdk => tangelo}/backendbuddy/translator/qdk_template.py (97%) rename {qsdk => tangelo}/backendbuddy/translator/translate_braket.py (98%) rename {qsdk => tangelo}/backendbuddy/translator/translate_cirq.py (98%) rename {qsdk => tangelo}/backendbuddy/translator/translate_json_ionq.py (97%) rename {qsdk => tangelo}/backendbuddy/translator/translate_openqasm.py (98%) rename {qsdk => tangelo}/backendbuddy/translator/translate_projectq.py (98%) rename {qsdk => tangelo}/backendbuddy/translator/translate_qdk.py (98%) rename {qsdk => tangelo}/backendbuddy/translator/translate_qiskit.py (98%) rename {qsdk => tangelo}/backendbuddy/translator/translate_qulacs.py (98%) rename {qsdk => tangelo}/helpers/__init__.py (86%) rename {qsdk => tangelo}/helpers/utils.py (97%) rename {qsdk => tangelo}/molecule_library.py (97%) rename {qsdk => tangelo}/problem_decomposition/__init__.py (92%) rename {qsdk/algorithms/variational/tests => tangelo/problem_decomposition/dmet}/__init__.py (91%) rename {qsdk => tangelo}/problem_decomposition/dmet/_helpers/__init__.py (95%) rename {qsdk => tangelo}/problem_decomposition/dmet/_helpers/dmet_bath.py (99%) rename {qsdk => tangelo}/problem_decomposition/dmet/_helpers/dmet_fragment.py (98%) rename {qsdk => tangelo}/problem_decomposition/dmet/_helpers/dmet_onerdm.py (98%) rename {qsdk => tangelo}/problem_decomposition/dmet/_helpers/dmet_orbitals.py (99%) rename {qsdk => tangelo}/problem_decomposition/dmet/_helpers/dmet_scf.py (98%) rename {qsdk => tangelo}/problem_decomposition/dmet/_helpers/dmet_scf_guess.py (97%) rename {qsdk => tangelo}/problem_decomposition/dmet/dmet_problem_decomposition.py (98%) rename {qsdk => tangelo}/problem_decomposition/dmet/fragment.py (94%) rename {qsdk => tangelo}/problem_decomposition/electron_localization/__init__.py (92%) rename {qsdk => tangelo}/problem_decomposition/electron_localization/iao_localization.py (99%) rename {qsdk => tangelo}/problem_decomposition/electron_localization/meta_lowdin_localization.py (95%) create mode 100644 tangelo/problem_decomposition/oniom/__init__.py create mode 100644 tangelo/problem_decomposition/oniom/_helpers/__init__.py rename {qsdk => tangelo}/problem_decomposition/oniom/_helpers/helper_classes.py (98%) rename {qsdk => tangelo}/problem_decomposition/oniom/oniom_problem_decomposition.py (96%) rename {qsdk => tangelo}/problem_decomposition/problem_decomposition.py (94%) create mode 100644 tangelo/problem_decomposition/tests/__init__.py create mode 100644 tangelo/problem_decomposition/tests/dmet/__init__.py rename {qsdk => tangelo}/problem_decomposition/tests/dmet/data/test_dmet_oneshot_loop_core_rdm.txt (100%) rename {qsdk => tangelo}/problem_decomposition/tests/dmet/data/test_dmet_oneshot_loop_low_rdm.txt (100%) rename {qsdk => tangelo}/problem_decomposition/tests/dmet/data/test_dmet_orbitals.txt (100%) rename {qsdk => tangelo}/problem_decomposition/tests/dmet/test_dmet.py (95%) rename {qsdk => tangelo}/problem_decomposition/tests/dmet/test_dmet_fragment.py (94%) rename {qsdk => tangelo}/problem_decomposition/tests/dmet/test_dmet_oneshot_loop.py (89%) rename {qsdk => tangelo}/problem_decomposition/tests/dmet/test_dmet_orbitals.py (92%) rename {qsdk => tangelo}/problem_decomposition/tests/dmet/test_dmet_vqe.py (94%) create mode 100644 tangelo/problem_decomposition/tests/oniom/__init__.py rename {qsdk => tangelo}/problem_decomposition/tests/oniom/test_oniom.py (96%) create mode 100644 tangelo/toolboxes/__init__.py create mode 100644 tangelo/toolboxes/ansatz_generator/__init__.py rename {qsdk => tangelo}/toolboxes/ansatz_generator/_general_unitary_cc.py (99%) rename {qsdk => tangelo}/toolboxes/ansatz_generator/_hea_circuit.py (95%) rename {qsdk => tangelo}/toolboxes/ansatz_generator/_unitary_cc_openshell.py (99%) rename {qsdk => tangelo}/toolboxes/ansatz_generator/_unitary_cc_paired.py (94%) rename {qsdk => tangelo}/toolboxes/ansatz_generator/adapt_ansatz.py (94%) rename {qsdk => tangelo}/toolboxes/ansatz_generator/ansatz.py (97%) rename {qsdk => tangelo}/toolboxes/ansatz_generator/ansatz_utils.py (95%) rename {qsdk => tangelo}/toolboxes/ansatz_generator/fermionic_operators.py (96%) rename {qsdk => tangelo}/toolboxes/ansatz_generator/hea.py (96%) rename {qsdk => tangelo}/toolboxes/ansatz_generator/penalty_terms.py (96%) rename {qsdk => tangelo}/toolboxes/ansatz_generator/rucc.py (98%) create mode 100644 tangelo/toolboxes/ansatz_generator/tests/__init__.py rename {qsdk => tangelo}/toolboxes/ansatz_generator/tests/test_adapt_ansatz.py (87%) rename {qsdk => tangelo}/toolboxes/ansatz_generator/tests/test_fermionic_operators.py (95%) rename {qsdk => tangelo}/toolboxes/ansatz_generator/tests/test_general_unitary_cc.py (97%) rename {qsdk => tangelo}/toolboxes/ansatz_generator/tests/test_hea.py (93%) rename {qsdk => tangelo}/toolboxes/ansatz_generator/tests/test_penalty_terms.py (96%) rename {qsdk => tangelo}/toolboxes/ansatz_generator/tests/test_rucc.py (96%) rename {qsdk => tangelo}/toolboxes/ansatz_generator/tests/test_uccsd.py (94%) rename {qsdk => tangelo}/toolboxes/ansatz_generator/tests/test_upccgsd.py (94%) rename {qsdk => tangelo}/toolboxes/ansatz_generator/tests/test_variational_circuit.py (92%) rename {qsdk => tangelo}/toolboxes/ansatz_generator/uccsd.py (97%) rename {qsdk => tangelo}/toolboxes/ansatz_generator/upccgsd.py (97%) rename {qsdk => tangelo}/toolboxes/ansatz_generator/variational_circuit.py (94%) rename {qsdk => tangelo}/toolboxes/measurements/__init__.py (93%) rename {qsdk => tangelo}/toolboxes/measurements/estimate_measurements.py (97%) rename {qsdk => tangelo}/toolboxes/measurements/qubit_terms_grouping.py (97%) create mode 100644 tangelo/toolboxes/measurements/tests/__init__.py rename {qsdk => tangelo}/toolboxes/measurements/tests/data (100%) rename {qsdk => tangelo}/toolboxes/measurements/tests/test_measurements.py (92%) rename {qsdk => tangelo}/toolboxes/measurements/tests/test_qubit_terms_grouping.py (93%) create mode 100644 tangelo/toolboxes/molecular_computation/__init__.py rename {qsdk => tangelo}/toolboxes/molecular_computation/frozen_orbitals.py (98%) rename {qsdk => tangelo}/toolboxes/molecular_computation/molecule.py (98%) rename {qsdk => tangelo}/toolboxes/molecular_computation/rdms.py (100%) create mode 100644 tangelo/toolboxes/molecular_computation/tests/__init__.py rename {qsdk => tangelo}/toolboxes/molecular_computation/tests/test_frozen_orbitals.py (86%) rename {qsdk => tangelo}/toolboxes/molecular_computation/tests/test_molecule.py (96%) rename {qsdk => tangelo}/toolboxes/operators/__init__.py (93%) rename {qsdk => tangelo}/toolboxes/operators/operators.py (97%) create mode 100644 tangelo/toolboxes/operators/tests/__init__.py rename {qsdk => tangelo}/toolboxes/operators/tests/test_operators.py (94%) rename {qsdk => tangelo}/toolboxes/post_processing/__init__.py (92%) rename {qsdk => tangelo}/toolboxes/post_processing/bootstrapping.py (97%) rename {qsdk => tangelo}/toolboxes/post_processing/mc_weeny_rdm_purification.py (98%) create mode 100644 tangelo/toolboxes/post_processing/tests/__init__.py rename {qsdk => tangelo}/toolboxes/post_processing/tests/data/rdm1_nw.npy (100%) rename {qsdk => tangelo}/toolboxes/post_processing/tests/data/rdm1_nw_exact.npy (100%) rename {qsdk => tangelo}/toolboxes/post_processing/tests/data/rdm2_nw.npy (100%) rename {qsdk => tangelo}/toolboxes/post_processing/tests/data/rdm2_nw_exact.npy (100%) rename {qsdk => tangelo}/toolboxes/post_processing/tests/data/rdm2_spin_pot7_exact.npy (100%) rename {qsdk => tangelo}/toolboxes/post_processing/tests/data/rdm2_spin_pot7_experimental.npy (100%) rename {qsdk => tangelo}/toolboxes/post_processing/tests/test_mcweeny_purification.py (95%) rename {qsdk => tangelo}/toolboxes/qubit_mappings/__init__.py (93%) rename {qsdk => tangelo}/toolboxes/qubit_mappings/bravyi_kitaev.py (97%) rename {qsdk => tangelo}/toolboxes/qubit_mappings/jordan_wigner.py (97%) rename {qsdk => tangelo}/toolboxes/qubit_mappings/mapping_transform.py (96%) rename {qsdk => tangelo}/toolboxes/qubit_mappings/statevector_mapping.py (95%) rename {qsdk => tangelo}/toolboxes/qubit_mappings/symmetry_conserving_bravyi_kitaev.py (99%) create mode 100644 tangelo/toolboxes/qubit_mappings/tests/__init__.py rename {qsdk => tangelo}/toolboxes/qubit_mappings/tests/test_bravyi_kitaev.py (80%) rename {qsdk => tangelo}/toolboxes/qubit_mappings/tests/test_mapping_transform.py (96%) rename {qsdk => tangelo}/toolboxes/qubit_mappings/tests/test_qubitizer.py (94%) rename {qsdk => tangelo}/toolboxes/qubit_mappings/tests/test_statevector_mapping.py (94%) diff --git a/.github/workflows/github_actions_automated_testing.yml b/.github/workflows/github_actions_automated_testing.yml index 472d52e88..66672c42a 100755 --- a/.github/workflows/github_actions_automated_testing.yml +++ b/.github/workflows/github_actions_automated_testing.yml @@ -44,18 +44,18 @@ jobs: $(which dotnet-iqsharp) install --user pip install qsharp - - name: qsdk install + - name: tangelo install run: | python -m pip install . if: always() - - name: qsdk tests + - name: tangelo tests run: | - cd qsdk + cd tangelo pytest --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html if: always() - - name: qsdk notebooks as tests + - name: tangelo notebooks as tests run: | cd examples pytest --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html test_notebooks.py diff --git a/Dockerfile b/Dockerfile index fae6ba620..f615cadaf 100755 --- a/Dockerfile +++ b/Dockerfile @@ -21,11 +21,11 @@ RUN pip3 install --upgrade pip RUN pip3 install h5py==2.9.0 ipython jupyter setuptools wheel sphinx py3Dmol sphinx_rtd_theme nbsphinx scikit-build # Copy and set root directory, -ENV PYTHONPATH=/root/qsdk:$PYTHONPATH +ENV PYTHONPATH=/root/tangelo:$PYTHONPATH WORKDIR /root/ COPY . /root -# Install qSDK and its immediate dependencies (pyscf, openfermion, ...) +# Install Tangelo and its immediate dependencies (pyscf, openfermion, ...) RUN python3 /root/setup.py install # Install Microsoft QDK qsharp package diff --git a/LICENSE b/LICENSE index 2ef4bc754..6d5bf6267 100755 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/README.rst b/README.rst index 2b66e5f67..9dca749cb 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,4 @@ -qSDK overview +Tangelo overview ============= Welcome ! @@ -41,19 +41,14 @@ root directory: If the installation of a dependency fails and the reason is not obvious, we suggest installing that dependency separately with ``pip``\ , before trying again. -If you would like to modify or develop code in ``qSDK``\ , you can add the path to this folder to your ``PYTHONPATH`` -environment variable instead of installing it with pip: - -.. code-block:: - - export PYTHONPATH=:$PYTHONPATH +You can also add the path to this folder to your ``PYTHONPATH`` environment variable to facilitate your own developments. Optional dependencies ^^^^^^^^^^^^^^^^^^^^^ -qSDK enables users to target various backends. In particular, it integrates quantum circuit simulators such as +Tangelo enables users to target various backends. In particular, it integrates quantum circuit simulators such as ``qulacs``\ , ``qiskit``\ , ``cirq`` or ``qdk``. We leave it to you to install the packages of your choice. -Backends such as ``qulacs`` and ``cirq`` show good overall performance. Most packages can be installed through pip in a straightforward way: +Most packages can be installed through pip in a straightforward way: .. code-block:: @@ -91,7 +86,7 @@ Tests ----- Unit tests can be found in the ``tests`` folders, located in the various toolboxes they are related to. To automatically -find and run all tests (assuming you are in the ``qsdk`` subfolder that contains the code of the package): +find and run all tests (assuming you are in the ``tangelo`` subfolder that contains the code of the package): .. code-block:: @@ -100,8 +95,8 @@ find and run all tests (assuming you are in the ``qsdk`` subfolder that contains Citations --------- -If you use qSDK in your research, please cite +If you use Tangelo in your research, please cite -[TODO: this is a placeholder for our qSDK paper, to be written and put on arxiv in October] +[TODO: this is a placeholder for our Tangelo paper, to be written and put on arxiv in October] -Copyright 1QBit 2021. This software is released under the Apache Software License version 2.0. +Copyright Good Chemistry Company 2021. This software is released under the Apache Software License version 2.0. diff --git a/dev_tools/build_sphinx_docs.sh b/dev_tools/build_sphinx_docs.sh index 41a99d6de..2ca52487d 100644 --- a/dev_tools/build_sphinx_docs.sh +++ b/dev_tools/build_sphinx_docs.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Make sure you build qsdk and all relevant dependencies before attempting to generate documentations +# Make sure you build tangelo and all relevant dependencies before attempting to generate documentations # or mock the desired packages in docs/source/conf.py cd ../docs || cd docs pip install sphinx sphinx_rtd_theme nbsphinx @@ -13,5 +13,5 @@ pip install -r requirements.txt sudo apt-get install -y latexmk texlive-latex-recommended texlive-latex-extra texlive-fonts-recommended pandoc dvipng # Build html documentation in ../docs/source/html -sphinx-apidoc -o source ../qsdk +sphinx-apidoc -o source ../tangelo make clean; make html diff --git a/docs/source/conf.py b/docs/source/conf.py index cf0165777..40106912d 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -17,8 +17,8 @@ # -- Project information ----------------------------------------------------- -project = 'qsdk' -copyright = '2021 1QBit' +project = 'tangelo' +copyright = '2021 Good Chemistry Company' author = 'Valentin senicourt, Alexandre Fleury, Ryan Day, James Brown' # The full version, including alpha/beta/rc tags diff --git a/docs/source/index.rst b/docs/source/index.rst index c08e4792b..6d0b5022f 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,9 +1,9 @@ -.. qsdk documentation master file, created by +.. tangelo documentation master file, created by sphinx-quickstart on Fri Oct 15 15:20:40 2021. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to qsdk's documentation! +Welcome to tangelo's documentation! ================================ .. toctree:: @@ -12,7 +12,7 @@ Welcome to qsdk's documentation! README tutorials - qsdk + tangelo Indices and tables ================== diff --git a/examples/adapt.ipynb b/examples/adapt.ipynb index 29e76e10a..8dce2522d 100755 --- a/examples/adapt.ipynb +++ b/examples/adapt.ipynb @@ -34,7 +34,7 @@ "\n", "In ADAPT-VQE, an ansatz which approximates not UCCSD/UCCGSD, but in fact FCI, is built iteratively. Over a series of cycles, the ansatz circuit is grown to achieve an approximation to FCI with a minimal number of circuit elements. In this way, ADAPT-VQE can be thought as a meta-VQE: at each cycle, a new ansatz is defined, and its parameters optimized according to conventional VQE. As the cycles proceed, the ansatz grows in both complexity and expressibility. This algorithm comes at the expense of a significant increase in measurement overhead. In order to identify the best operator to append to the present ansatz circuit, a large number of measurements are performed to rank the available operators in order of their ability to further reduce the ansatz state energy.\n", "\n", - "In this notebook, we explore the implementation of this algorithm, available in qSDK. The original algorithm is examined first, and has shown some success in reducing the number of variational parameters required to express the quantum state. Then, we examine another version of ADAPT-VQE which is successful at reducing the circuit size by using a pool of operators defined from the Qubit Hamiltonian." + "In this notebook, we explore the implementation of this algorithm, available in Tangelo. The original algorithm is examined first, and has shown some success in reducing the number of variational parameters required to express the quantum state. Then, we examine another version of ADAPT-VQE which is successful at reducing the circuit size by using a pool of operators defined from the Qubit Hamiltonian." ], "metadata": {} }, @@ -67,8 +67,8 @@ "cell_type": "code", "execution_count": null, "source": [ - "from qsdk import SecondQuantizedMolecule\n", - "from qsdk.algorithms import ADAPTSolver\n", + "from tangelo import SecondQuantizedMolecule\n", + "from tangelo.algorithms import ADAPTSolver\n", "\n", "H4 = [(\"H\", (0, 0, 0)), (\"H\", (0, 1.4, 0)), (\"H\", (0, 2.8, 0)), (\"H\", (0, 4.2, 0))]\n", "mol = SecondQuantizedMolecule(H4, q=0, spin=0, basis=\"sto-3g\", frozen_orbitals=[])\n", @@ -98,7 +98,7 @@ "source": [ "import matplotlib.pyplot as plt\n", "import numpy as np\n", - "from qsdk.algorithms import FCISolver\n", + "from tangelo.algorithms import FCISolver\n", "\n", "fci_solver = FCISolver(mol)\n", "exact = fci_solver.simulate()\n", @@ -125,7 +125,7 @@ "cell_type": "code", "execution_count": null, "source": [ - "from qsdk.algorithms import VQESolver, BuiltInAnsatze\n", + "from tangelo.algorithms import VQESolver, BuiltInAnsatze\n", "\n", "vqe_solver = VQESolver({'molecule': mol, 'ansatz': BuiltInAnsatze.UCCSD})\n", "vqe_solver.build()\n", @@ -204,7 +204,7 @@ " pool_generators (list of QubitOperator): list of generators\n", " \"\"\"\n", " import numpy as np\n", - " from qsdk.toolboxes.operators.operators import QubitOperator\n", + " from tangelo.toolboxes.operators.operators import QubitOperator\n", "\n", " pauli_lookup = {'Z':1, 'X':2, 'Y':3}\n", " pauli_reverse_lookup = ['I', 'Z', 'X', 'Y']\n", @@ -255,7 +255,7 @@ "cell_type": "code", "execution_count": null, "source": [ - "from qsdk.toolboxes.operators import QubitOperator\n", + "from tangelo.toolboxes.operators import QubitOperator\n", "qubit_operator = QubitOperator(((0, 'X'), (1, 'X'), (2, 'Y'), (3, 'Y')), -1.0) \\\n", " + QubitOperator(((0, 'X'), (1, 'Y'), (2, 'Y'), (3, 'X')), 1.0) \\\n", " + QubitOperator(((0, 'Y'), (1, 'X'), (2, 'X'), (3, 'Y')), 1.0) \\\n", @@ -285,8 +285,8 @@ "cell_type": "code", "execution_count": null, "source": [ - "from qsdk.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping\n", - "from qsdk.toolboxes.operators import count_qubits\n", + "from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping\n", + "from tangelo.toolboxes.operators import count_qubits\n", "\n", "fermion_operator = mol._get_fermionic_hamiltonian()\n", "qubit_operator = fermion_to_qubit_mapping(fermion_operator, 'jw', mol.n_active_sos, mol.n_electrons)\n", @@ -306,7 +306,7 @@ "cell_type": "code", "execution_count": null, "source": [ - "from qsdk.algorithms import ADAPTSolver\n", + "from tangelo.algorithms import ADAPTSolver\n", "\n", "opt_dict = {\"molecule\": mol,\n", " \"frozen_orbitals\": 0,\n", @@ -425,8 +425,8 @@ "cell_type": "code", "execution_count": null, "source": [ - "from qsdk.toolboxes.ansatz_generator.penalty_terms import combined_penalty\n", - "from qsdk.toolboxes.operators.operators import qubitop_to_qubitham\n", + "from tangelo.toolboxes.ansatz_generator.penalty_terms import combined_penalty\n", + "from tangelo.toolboxes.operators.operators import qubitop_to_qubitham\n", "\n", "# Define dictionary of desired quantum numbers [penalty_weight, desired_quantum_number]\n", "penalty_weight = 1/2\n", @@ -497,7 +497,7 @@ "source": [ "## Conclusion\n", "\n", - "In this notebook, we've explored an implementation of the original ADAPT-VQE algorithm, and the Hamiltonian-inspired qubit variant, using the tools available in qSDK. It is clear that the number of parameters required for accurate results can be made much smaller with the orignal algorithm, while the qubit version can reduce the circuit depth greatly. The second section illustrates how users can create their own pool of qubit operators through their own `get_pool` function, to explore their own avenues. " + "In this notebook, we've explored an implementation of the original ADAPT-VQE algorithm, and the Hamiltonian-inspired qubit variant, using the tools available in Tangelo. It is clear that the number of parameters required for accurate results can be made much smaller with the orignal algorithm, while the qubit version can reduce the circuit depth greatly. The second section illustrates how users can create their own pool of qubit operators through their own `get_pool` function, to explore their own avenues. " ], "metadata": {} } diff --git a/examples/backendbuddy/1.the_basics.ipynb b/examples/backendbuddy/1.the_basics.ipynb index d5a272d82..1f828193e 100755 --- a/examples/backendbuddy/1.the_basics.ipynb +++ b/examples/backendbuddy/1.the_basics.ipynb @@ -11,7 +11,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "`backendbuddy` is a submodule of `qsdk` that helps you connect to and leverage the features of various backends, may they be simulators or QPUs. This notebook talks about the abstract format used in this python package to represent a quantum circuit, and how we can then translate it to other formats or objects used in popular packages such as Qiskit, ProjectQ, Qulacs (...) and leverage the different features and performance they offer.\n", + "`backendbuddy` is a submodule of `tangelo` that helps you connect to and leverage the features of various backends, may they be simulators or QPUs. This notebook talks about the abstract format used in this python package to represent a quantum circuit, and how we can then translate it to other formats or objects used in popular packages such as Qiskit, ProjectQ, Qulacs (...) and leverage the different features and performance they offer.\n", "\n", "This functionality is pretty useful because it means that you can derive a given quantum circuit for any supported compute backend available with minimal effort, whether it is a simulator or an actual QPU. You therefore do not need to rewrite your program to run on a different platform, or to share your code in a specific format that your collaborators or clients expect, for publication or running an actual hardware experiment. It also means that whatever new method or code you develop can now suddenly run on all the compute backends available, enabling researchers and product users to benefit from your contributions regardless of the compute platform they intend to use.\n", "\n", @@ -34,7 +34,7 @@ "source": [ "## Requirements\n", "\n", - "In order to run the contents of this notebook, you need to have the `qsdk` package installed in your python environment. Some cells may require that a specific backend (such as `qiskit`, `qulacs`...) is installed to run.\n", + "In order to run the contents of this notebook, you need to have the `tangelo` package installed in your python environment. Some cells may require that a specific backend (such as `qiskit`, `qulacs`...) is installed to run.\n", "\n", "Please have a look at the installation instructions, if you need.\n", "\n", @@ -85,7 +85,7 @@ } ], "source": [ - "from qsdk.backendbuddy import Gate\n", + "from tangelo.backendbuddy import Gate\n", "\n", "# Create a Hadamard gate acting on qubit 2\n", "H_gate = Gate(\"H\", 2)\n", @@ -166,7 +166,7 @@ } ], "source": [ - "from qsdk.backendbuddy import Circuit\n", + "from tangelo.backendbuddy import Circuit\n", "\n", "# Here's a list of abstract gates\n", "mygates = [Gate(\"H\", 2), Gate(\"CNOT\", 1, control=0), Gate(\"CNOT\", target=2, control=1),\n", @@ -266,7 +266,7 @@ } ], "source": [ - "from qsdk.backendbuddy.helpers.circuits import measurement_basis_gates, pauli_string_to_of\n", + "from tangelo.backendbuddy.helpers.circuits import measurement_basis_gates, pauli_string_to_of\n", "\n", "def theta_sweep(theta, m_basis):\n", " \"\"\" A single-parameter circuit, with change of basis at the end if needed \"\"\"\n", @@ -316,7 +316,7 @@ } ], "source": [ - "from qsdk.backendbuddy import SUPPORTED_GATES\n", + "from tangelo.backendbuddy import SUPPORTED_GATES\n", "\n", "for backend, gates in SUPPORTED_GATES.items():\n", " print(f'{backend} : {gates}')" @@ -330,7 +330,7 @@ "\n", "**Note**: Do not use the `MEASURE` Gate in your noiseless/shotless simulations, unless your circuit requires a mid-circuit measurement. This gate is intended for the simulation of **mixed states**. \n", "\n", - "**Note**: If you actually had a closer look at some simulation backends, such as Qiskit, Qulacs or ProjectQ, you'd find out that none of them have the same definition for the $R_z(\\theta)$ operation: they all differ up to a phase or sign convention for $\\theta$! Since the outcome of your simulation should not depend on the target backend, the translation step enforce a given convention, detailed in the documentation. In particular, this implies that the native circuit written for a given backend may not be equivalent to the same one written in abstract format, and then translated to this backend. Be mindful of that as you try to take some code written for a given backend, to port it to `qsdk`.\n", + "**Note**: If you actually had a closer look at some simulation backends, such as Qiskit, Qulacs or ProjectQ, you'd find out that none of them have the same definition for the $R_z(\\theta)$ operation: they all differ up to a phase or sign convention for $\\theta$! Since the outcome of your simulation should not depend on the target backend, the translation step enforce a given convention, detailed in the documentation. In particular, this implies that the native circuit written for a given backend may not be equivalent to the same one written in abstract format, and then translated to this backend. Be mindful of that as you try to take some code written for a given backend, to port it to `tangelo`.\n", "\n", "Below, we show how the translation function returns backend-specific objects, all with their usual built-in functionalities! See how the print function behaves differently for each of them for instance, and remember that you can use their built-in methods to accomplish many things (ex: after translating your circuit into a `Qiskit.QuantumCircuit` object, you can use the `draw` method to export it in a nice format: https://qiskit.org/documentation/stubs/qiskit.circuit.QuantumCircuit.draw.html). \n", "\n", @@ -427,7 +427,7 @@ } ], "source": [ - "from qsdk.backendbuddy.translator import translate_qulacs, translate_qiskit, translate_projectq, translate_qsharp, translate_openqasm, translate_cirq, translate_json_ionq, translate_braket\n", + "from tangelo.backendbuddy.translator import translate_qulacs, translate_qiskit, translate_projectq, translate_qsharp, translate_openqasm, translate_cirq, translate_json_ionq, translate_braket\n", "\n", "circ3_projectq = translate_projectq(circuit3)\n", "print(f\"{circ3_projectq}\\n\")\n", @@ -640,7 +640,7 @@ } ], "source": [ - "from qsdk.backendbuddy import Simulator, backend_info\n", + "from tangelo.backendbuddy import Simulator, backend_info\n", "\n", "for backend, info in backend_info.items():\n", " print(f'{backend} : {info}')" @@ -859,9 +859,9 @@ ], "metadata": { "kernelspec": { - "display_name": "qsdk_sept_21", + "display_name": "tangelo_sept_21", "language": "python", - "name": "qsdk_sept_21" + "name": "tangelo_sept_21" }, "language_info": { "codemirror_mode": { diff --git a/examples/backendbuddy/3.noisy_simulation.ipynb b/examples/backendbuddy/3.noisy_simulation.ipynb index 51cbb368f..be41c3df4 100755 --- a/examples/backendbuddy/3.noisy_simulation.ipynb +++ b/examples/backendbuddy/3.noisy_simulation.ipynb @@ -34,7 +34,7 @@ "source": [ "## Requirements\n", "\n", - "In order to run the contents of this notebook, I simply recommend that you install `qsdk` as per the installation instructions, as well as one or several backends supporting noisy simulation (cirq, qiskit, qulacs...). You can install them with `pip`.\n", + "In order to run the contents of this notebook, I simply recommend that you install `tangelo` as per the installation instructions, as well as one or several backends supporting noisy simulation (cirq, qiskit, qulacs...). You can install them with `pip`.\n", "\n", "---" ] @@ -71,7 +71,7 @@ } ], "source": [ - "from qsdk.backendbuddy.noisy_simulation import NoiseModel, SUPPORTED_NOISE_MODELS\n", + "from tangelo.backendbuddy.noisy_simulation import NoiseModel, SUPPORTED_NOISE_MODELS\n", "\n", "print(f'Supported noise models: {SUPPORTED_NOISE_MODELS}\\n')\n", "\n", @@ -106,7 +106,7 @@ } ], "source": [ - "from qsdk.backendbuddy import ONE_QUBIT_GATES, TWO_QUBIT_GATES\n", + "from tangelo.backendbuddy import ONE_QUBIT_GATES, TWO_QUBIT_GATES\n", "\n", "nm = NoiseModel()\n", "\n", @@ -160,7 +160,7 @@ } ], "source": [ - "from qsdk.backendbuddy import backend_info\n", + "from tangelo.backendbuddy import backend_info\n", "from pprint import pprint \n", "\n", "pprint(backend_info)" @@ -189,7 +189,7 @@ } ], "source": [ - "from qsdk.backendbuddy import Simulator, Gate, Circuit\n", + "from tangelo.backendbuddy import Simulator, Gate, Circuit\n", "\n", "# Define a noise model\n", "nmp = NoiseModel()\n", @@ -367,7 +367,7 @@ } ], "source": [ - "from qsdk.backendbuddy.noisy_simulation import get_qiskit_noise_model\n", + "from tangelo.backendbuddy.noisy_simulation import get_qiskit_noise_model\n", "\n", "nm = NoiseModel()\n", "nm.add_quantum_error('RX', 'depol', 4/3)\n", diff --git a/examples/backendbuddy/VQE_H2.py b/examples/backendbuddy/VQE_H2.py index 61ed1169c..3e848bb23 100755 --- a/examples/backendbuddy/VQE_H2.py +++ b/examples/backendbuddy/VQE_H2.py @@ -9,7 +9,7 @@ import numpy as np from scipy.optimize import minimize -from qsdk.backendbuddy import Gate, Circuit, Simulator +from tangelo.backendbuddy import Gate, Circuit, Simulator class H2StatePreparationCircuit(Circuit): diff --git a/examples/backendbuddy/custom_ansatz.py b/examples/backendbuddy/custom_ansatz.py index 6b79e0542..5d65eb88e 100755 --- a/examples/backendbuddy/custom_ansatz.py +++ b/examples/backendbuddy/custom_ansatz.py @@ -21,7 +21,7 @@ change significantly or "grow" (such as ADAPT-VQE). """ -from qsdk.backendbuddy import Gate, Circuit +from tangelo.backendbuddy import Gate, Circuit class MyAnsatzCircuit(Circuit): diff --git a/examples/backendbuddy/import_projectq_circuit.py b/examples/backendbuddy/import_projectq_circuit.py deleted file mode 100755 index be45a6a9e..000000000 --- a/examples/backendbuddy/import_projectq_circuit.py +++ /dev/null @@ -1,61 +0,0 @@ -""" - This example shows how users can perform surgery in their ProjectQ scripts to retrieve the circuit - using the CommandPrinter engine, and then import it into qsdk.backendbuddy, to simulate it with something else - such as a noisy simulator or a more performant / scalable simulator, such as qulacs. - - It relies on a function in the translator module called projectq2abs. -""" - -from projectq import MainEngine -from projectq.ops import H, X, CNOT - -# 1: Use CommandPrinter as a your ProjectQ engine backend. This engine just prints the commands run by ProjectQ. -# For more info about ProjectQ and how to swap and combine backends: -# https://github.com/1QB-Information-Technologies/Feynman/blob/master/1qbit_tutorial/1QBit_projectq_tutorial_python3.ipynb -from projectq.backends import CommandPrinter -eng = MainEngine(backend=CommandPrinter(accept_input=False)) -# NB: if you're using openfermion-projectq, you might be using their uccsd trotter engine and have something like this: -# eng = uccsd_trotter_engine(compiler_backend=CommandPrinter(accept_input=False)) - - -# 2: Before "simulation", swap standard output with StringIo object. -# The CommandPrinter engine will "print" to this object instead of your screen -import sys -from io import StringIO -s = StringIO() -sys.stdout, s = s, sys.stdout - - -# 3: Allocate, simulate, deallocate as usual. -# Here, I use a dummy circuit to construct a Bell pair, for the sake of example -# Only a subset of ProjectQ instructions are supported by qsdk.backendbuddy. -# NB: `Measure` instructions are currently ignored. This is only an issue if you have mid-circuit measurements. -qreg = eng.allocate_qureg(2) -eng.flush() -H | qreg[0] -CNOT | (qreg[1], qreg[0]) -eng.flush(deallocate_qubits=True) - - -# 4: Restore standard output (swap back). We can grab the ProjectQ instructions from the StringIo object with `getvalue` -sys.stdout, s = s, sys.stdout -pq_circ = s.getvalue() -print(f"*** Your ProjectQ instructions, as shown by the CommandPrinter engine:\n{pq_circ}\n") - - -# 5: qsdk.backendbuddy can translate the projectq instructions into its abstract format -# The resulting circuit can then be translated in various formats or run on different compute backends. -from qsdk.backendbuddy import translator -abs_circ = translator._translate_projectq2abs(pq_circ) -print(f"*** The corresponding qsdk.backendbuddy abstract circuit:\n{abs_circ}\n") - - -# 6: Do your thing. Example: using qulacs as a simulation backend, using qsdk.backendbuddy. -# Check out 'the_basics.ipynb` python jupyter notebook for more info -from qsdk.backendbuddy import Simulator -cirq_sim = Simulator(target='cirq') -cirq_freqs = cirq_sim.simulate(abs_circ) -print(f"*** Simulation results (frequencies):\n{cirq_freqs}") -# For an expectation value, assume you have an openfermion-like operator called qubit_op -# cirq_expval = qulacs_sim.get_expectation_value(qubit_op, abs_circ) -# print(f"{cirq_expval}") diff --git a/examples/backendbuddy/ionq_postprocessing.py b/examples/backendbuddy/ionq_postprocessing.py deleted file mode 100755 index f76d290be..000000000 --- a/examples/backendbuddy/ionq_postprocessing.py +++ /dev/null @@ -1,55 +0,0 @@ -""" - Example of how to process IonQ QPU results to compute some expectation values. - This example assumes that a QPU experiment was run on a IonQ device and that a histogram of results was returned. - - The histogram is first translated in standard format, and backendbuddy is then used to compute an - expectation values using these frequencies. - - If you wish to compute theoretical value using a simulator, please write your circuit in abstract format and - instantiate the Simulator class with a backend such as Qiskit, ProjectQ or qulacs, and just call the - "get_expectation_value" method. There is zero benefit in using the IonQ simulator, it does exactly the same. -""" - -from qsdk.backendbuddy import Simulator -from qsdk.backendbuddy.helpers import pauli_string_to_of - -# Example of IonQ QPU results written in a Python-friendly format -# The dictionary below contains 5 histograms corresponding to 5 different experiments, each for a different basis -QPU_results = {'XX': {'0': 0.2134, '1': 0.2935, '2': 0.2886, '3': 0.2045}, - 'XZ': {'0': 0.0054, '1': 0.0055, '2': 0.5069, '3': 0.4822}, - 'YY': {'1': 0.2132, '0': 0.291, '3': 0.2965, '2': 0.1993}, - 'ZX': {'0': 0.0132, '1': 0.4906, '2': 0.0068, '3': 0.4894}, - 'ZZ': {'0': 0.01, '1': 0.0054, '2': 0.0174, '3': 0.9672}} -n_qubits = 2 - - -def dd(d, n_qubits): - """ Remaps IonQ histogram keys to binary bit strings corresponding to classical state (q0q1...qn) """ - new_d = dict() - for k, v in d.items(): - bs = bin(int(k)).split('b')[-1] - new_k = "0" * (n_qubits - len(bs)) + bs - new_d[new_k[::-1]] = v - return new_d - - -def pauli_string_to_of_string(ps): - """ Turns strings of type XXIZ into strings like X0 X1 Z3, for convenience with expectation value functions """ - ofs = '' - for i, l in enumerate(ps): - if l != 'I': - ofs += (l + str(i) + ' ') - return ofs[:-1] - - -# Compute all experimental expectation values -# ------------------------------------------- -QPU_results = {term: dd(hist, n_qubits) for term, hist in QPU_results.items()} - -# Use experiment results as you see fit to compute your expectation values of interest -for term, hist in QPU_results.items(): - print(f'{term} {Simulator.get_expectation_value_from_frequencies_oneterm(term=pauli_string_to_of(term), frequencies=hist):.5f}') -print(f'ZI {Simulator.get_expectation_value_from_frequencies_oneterm(term=pauli_string_to_of("ZI"), frequencies=QPU_results["ZZ"]):.5f}') -print(f'IZ {Simulator.get_expectation_value_from_frequencies_oneterm(term=pauli_string_to_of("IZ"), frequencies=QPU_results["ZZ"]):.5f}') -print(f'IX {Simulator.get_expectation_value_from_frequencies_oneterm(term=pauli_string_to_of("IX"), frequencies=QPU_results["ZX"]):.5f}') -print(f'XI {Simulator.get_expectation_value_from_frequencies_oneterm(term=pauli_string_to_of("XI"), frequencies=QPU_results["XZ"]):.5f}') diff --git a/examples/backendbuddy/qsharp/qsharp_circuit_generation.py b/examples/backendbuddy/qsharp/qsharp_circuit_generation.py index 6e390a3cd..2fc0aee6c 100755 --- a/examples/backendbuddy/qsharp/qsharp_circuit_generation.py +++ b/examples/backendbuddy/qsharp/qsharp_circuit_generation.py @@ -9,9 +9,9 @@ Q# code needs to be written to file, as it needs to be compiled before execution, regardless of the compute backend. """ -from qsdk.backendbuddy import Gate, Circuit -from qsdk.backendbuddy.translator import translate_qsharp -from qsdk.backendbuddy.helper_circuits import measurement_basis_gates, pauli_string_to_of +from tangelo.backendbuddy import Gate, Circuit +from tangelo.backendbuddy.translator import translate_qsharp +from tangelo.backendbuddy.helper_circuits import measurement_basis_gates, pauli_string_to_of def theta_sweep(theta, m_basis): diff --git a/examples/backendbuddy/qsharp/qsharp_execution.py b/examples/backendbuddy/qsharp/qsharp_execution.py index f46d4b912..716da6a3d 100755 --- a/examples/backendbuddy/qsharp/qsharp_execution.py +++ b/examples/backendbuddy/qsharp/qsharp_execution.py @@ -8,11 +8,7 @@ Qubits are numbered left-to-right in the results, in both cases (e.g q0q1q2...) -In the case of Azure Quantum, the following tutorials and documentations may be helpful to you: -https://1qbit-intra.atlassian.net/wiki/spaces/QSD/pages/1319600305/Submit+monitor+Hardware+Experiments+on+Azure+Quantum -https://github.com/MicrosoftDocs/quantum-docs-private/wiki/ - -You can elegantly generate your Q# circuits using qsdk.backendbuddy and submit them right away in one single script. +You can elegantly generate your Q# circuits using tangelo.backendbuddy and submit them right away in one single script. """ @@ -60,5 +56,5 @@ # If your Q# operation takes no parameter (results need to be retrieved through Azure portal, under the relevant Quantum Workspace). job_id = qsharp.azure.submit(MyQsharpOperation, shots=n_shots, jobName=job_name) - # If your Q# operation takes parameters (currently not available in qsdk.backendbuddy, but you can write your own Q#): + # If your Q# operation takes parameters (currently not available in tangelo.backendbuddy, but you can write your own Q#): # job_id = qsharp.azure.submit(MyQsharpOperation, param1=value1, param2=value2, ..., shots=n_shots, jobName=job_name') diff --git a/examples/dmet.ipynb b/examples/dmet.ipynb index 4ed3a6f3b..9ec9a0930 100755 --- a/examples/dmet.ipynb +++ b/examples/dmet.ipynb @@ -15,7 +15,7 @@ "\n", "Such approaches enable us to study how combining classical and quantum algorithms may play a role in the resolution of problems beyond toy models, and even maybe provide a configurable and scalable path forward in the future, towards much more ambitious molecular systems.\n", "\n", - "In order to run this notebook, you need to install the `qsdk` python package, or add it to your PYTHONPATH." + "In order to run this notebook, you need to install the `tangelo` python package, or add it to your PYTHONPATH." ] }, { @@ -68,7 +68,7 @@ "source": [ "## 3. First example: DMET-CCSD on Butane \n", "\n", - "Before we proceed, let's import all the relevant data-structures and classes from `qsdk`:" + "Before we proceed, let's import all the relevant data-structures and classes from `tangelo`:" ] }, { @@ -81,16 +81,16 @@ "import json\n", "\n", "# Molecule definition.\n", - "from qsdk import SecondQuantizedMolecule\n", + "from tangelo import SecondQuantizedMolecule\n", "\n", "# The minimal import for DMET.\n", - "from qsdk.problem_decomposition.dmet.dmet_problem_decomposition import DMETProblemDecomposition\n", + "from tangelo.problem_decomposition.dmet.dmet_problem_decomposition import DMETProblemDecomposition\n", "# Ability to change localization method.\n", - "from qsdk.problem_decomposition.dmet.dmet_problem_decomposition import Localization\n", + "from tangelo.problem_decomposition.dmet.dmet_problem_decomposition import Localization\n", "# Use for VQE ressources estimation vs DMET.\n", - "from qsdk.algorithms import VQESolver\n", + "from tangelo.algorithms import VQESolver\n", "# Use for comparison.\n", - "from qsdk.algorithms import FCISolver" + "from tangelo.algorithms import FCISolver" ] }, { @@ -416,7 +416,7 @@ "source": [ "### 4.2 DMET-VQE\n", "\n", - "Here, we demonstrate how to perform DMET-VQE calculations using qSDK package. The aim is to obtain improved results (vs HF energy) when compairing to the Full CI method (without using problem decomposition) and also using a quantum algorithm (VQE)." + "Here, we demonstrate how to perform DMET-VQE calculations using Tangelo package. The aim is to obtain improved results (vs HF energy) when compairing to the Full CI method (without using problem decomposition) and also using a quantum algorithm (VQE)." ] }, { @@ -1242,7 +1242,7 @@ " 'electron_localization': ,\n", " 'fragment_atoms': [1, 1, 1, 1],\n", " 'fragment_solvers': ['ccsd', 'ccsd', 'ccsd', 'ccsd'],\n", - " 'optimizer': >,\n", + " 'optimizer': >,\n", " 'initial_chemical_potential': 0.0,\n", " 'solvers_options': [{}, {}, {}, {}],\n", " 'verbose': False,\n", @@ -1292,7 +1292,7 @@ " 'electron_localization': ,\n", " 'fragment_atoms': [1, 1, 1, 1],\n", " 'fragment_solvers': ['vqe', 'ccsd', 'fci', 'vqe'],\n", - " 'optimizer': >,\n", + " 'optimizer': >,\n", " 'initial_chemical_potential': 0.0,\n", " 'solvers_options': [{'qubit_mapping': 'jw',\n", " 'initial_var_params': 'ones',\n", @@ -1347,7 +1347,7 @@ " 'electron_localization': ,\n", " 'fragment_atoms': [1, 1, 1, 1],\n", " 'fragment_solvers': ['ccsd', 'ccsd', 'ccsd', 'ccsd'],\n", - " 'optimizer': >,\n", + " 'optimizer': >,\n", " 'initial_chemical_potential': 0.1,\n", " 'solvers_options': [{}, {}, {}, {}],\n", " 'verbose': False,\n", @@ -1397,7 +1397,7 @@ " 'electron_localization': ,\n", " 'fragment_atoms': [1, 1, 1, 1],\n", " 'fragment_solvers': ['vqe', 'vqe', 'vqe', 'vqe'],\n", - " 'optimizer': >,\n", + " 'optimizer': >,\n", " 'initial_chemical_potential': 0.0,\n", " 'solvers_options': [{'qubit_mapping': 'bk'},\n", " {'qubit_mapping': 'bk'},\n", @@ -1443,7 +1443,7 @@ " - S. Wouters, C.A. Jiménez-Hoyos, Q. Sun, and G.K.L. Chan, J. Chem. Theory Comput. 12, 2706 (2016).\n", " - G. Knizia and G.K.L. Chan, J. Chem. Theory Comput. 9, 1428 (2013).\n", " - G. Knizia and G.K.L. Chan, Phys. Rev. Lett. 109, 186404 (2012).\n", - "- 1QBit papers on the subject\n", + "- Good Chemistry Company papers on the subject\n", " - Y. Kawashima, M.P. Coons, Y. Nam, E. Lloyd, S. Matsuura, A.J. Garza, S. Johri, L. Huntington, V. Senicourt, A.O. Maksymov, J.H. V. Nguyen, J. Kim, N. Alidoust, A. Zaribafiyan, and T. Yamazaki, ArXiv:2102.07045 (2021).\n", " - T. Yamazaki, S. Matsuura, A. Narimani, A. Saidmuradov, and A. Zaribafiyan, ArXiv:1806.01305 (2018)." ] @@ -1451,9 +1451,9 @@ ], "metadata": { "kernelspec": { - "display_name": "qSDK", + "display_name": "Tangelo", "language": "python", - "name": "qsdk" + "name": "tangelo" }, "language_info": { "codemirror_mode": { diff --git a/examples/overview_endtoend.ipynb b/examples/overview_endtoend.ipynb index 62420deca..2b9bd6cd5 100644 --- a/examples/overview_endtoend.ipynb +++ b/examples/overview_endtoend.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# End-to-end Quantum Chemistry Workflow on Quantum Hardware with qSDK" + "# End-to-end Quantum Chemistry Workflow on Quantum Hardware with Tangelo" ] }, { @@ -37,7 +37,7 @@ " * [Statistical analysis of results](#92)\n", "* [10. Closing words](#10)\n", "\n", - "In this notebook, we illustrate how qSDK can be used to re-enact one of our recent successful quantum chemistry experiments, done in collaboration with Dow and IonQ [[Kawashima et al., 2021](https://arxiv.org/abs/2102.07045)]. This experiment features an electronic system that could not be realistically solved with a heads-on approach on an existing quantum computer, yet produced great experimental results by combining the DMET problem decomposition technique [[Knizia et al., 2013a](https://doi.org/10.1103/PhysRevLett.109.186404) and [Knizia et al., 2013b](https://doi.org/10.1021/ct301044e)] with the VQE algorithm [[Kassal et al., 2011](https://doi.org/10.1146/annurev-physchem-032210-103512), [Peruzzo et al., 2014](https://doi.org/10.1038/ncomms5213) and [Cao et al., 2019](https://doi.org/10.1021/acs.chemrev.8b00803)] and error-mitigation techniques [[Bharti et al., 2021](https://arxiv.org/abs/2101.08448)], running on an ion-trap-based quantum processor." + "In this notebook, we illustrate how Tangelo can be used to re-enact one of our recent successful quantum chemistry experiments, done in collaboration with Dow and IonQ [[Kawashima et al., 2021](https://arxiv.org/abs/2102.07045)]. This experiment features an electronic system that could not be realistically solved with a heads-on approach on an existing quantum computer, yet produced great experimental results by combining the DMET problem decomposition technique [[Knizia et al., 2013a](https://doi.org/10.1103/PhysRevLett.109.186404) and [Knizia et al., 2013b](https://doi.org/10.1021/ct301044e)] with the VQE algorithm [[Kassal et al., 2011](https://doi.org/10.1146/annurev-physchem-032210-103512), [Peruzzo et al., 2014](https://doi.org/10.1038/ncomms5213) and [Cao et al., 2019](https://doi.org/10.1021/acs.chemrev.8b00803)] and error-mitigation techniques [[Bharti et al., 2021](https://arxiv.org/abs/2101.08448)], running on an ion-trap-based quantum processor." ] }, { @@ -46,7 +46,7 @@ "source": [ "## Setting up your environment \n", "\n", - "In order to run this notebook, `qsdk` needs to be installed in your Python environment, or be found in your PYTHONPATH. Please refer to the installation instructions for any additional information, and how to install optional dependencies such as performant quantum circuit simulators." + "In order to run this notebook, `tangelo` needs to be installed in your Python environment, or be found in your PYTHONPATH. Please refer to the installation instructions for any additional information, and how to install optional dependencies such as performant quantum circuit simulators." ] }, { @@ -103,7 +103,7 @@ "source": [ "## Representing the input molecular data \n", "\n", - "First, we need to represent the molecular system of interest. Currently, qSDK accepts some xyz input that specifies individual atoms paired with their corresponding xyz cartesian coordinates. We can pass this information as nested lists containing the atom string and the coordinate tuple (see example below) or as a single string, specifying one atom per line and separating each coordinate with a space.\n", + "First, we need to represent the molecular system of interest. Currently, Tangelo accepts some xyz input that specifies individual atoms paired with their corresponding xyz cartesian coordinates. We can pass this information as nested lists containing the atom string and the coordinate tuple (see example below) or as a single string, specifying one atom per line and separating each coordinate with a space.\n", "\n", "The `openbabel` python package can be used to derive the cartesian coordinates from formats such as mol, mol2, ginp, com, pdb (...), and thus provides us with the ability to support your favorite format in the future, maybe even with your contribution to the codebase.\n", "\n", @@ -155,7 +155,7 @@ } ], "source": [ - "from qsdk import SecondQuantizedMolecule\n", + "from tangelo import SecondQuantizedMolecule\n", "\n", "xyz = [\n", " ['H', (0.0, 1.780, 0.0)], \n", @@ -202,7 +202,7 @@ } ], "source": [ - "from qsdk.algorithms import FCISolver, CCSDSolver\n", + "from tangelo.algorithms import FCISolver, CCSDSolver\n", "\n", "fci_energy = FCISolver(mol).simulate()\n", "print(f\"FCI energy: {fci_energy:.5f}\")\n", @@ -220,7 +220,7 @@ "Now that we have the reference energy, we can try to tackle the same problem with a quantum computer. In order to do that we have to choose an appropriate quantum algorithm that is able to calculate the ground state energy of a molecule. In this tutorial we choose the VQE algorithm. Note that all the other steps in this pipeline are compatible with other quantum algorithms that can be used for the same purpose (like the phase estimation algorithm).\n", "VQE is a popular algorithm, and due to it producing very shallow circuits, it is often the choice for proof of concept demonstrations on near-term quantum hardware. See our [VQE notebook](vqe.ipynb) for more details about this algorithm. Each quantum algorithm has requirements, i.e. its own unique building blocks and parameters. One important parameter to choose when working with VQE is the choice of strategy to build the parametric wave function (ansatz). At the beginning, we use the vanilla version of a well-known ansatze inspired by the Unitary Coupled Cluster operators in chemistry (a.k.a the UCC ansatze).\n", "\n", - "With this set up in mind, we can leverage qSDK to estimate the resources required and the cost of this experiment. Later in the document, we show how by leveraging qSDK and choosing smarter strategies to build an ansazte, one could turn a seemingly intractable problem into one easy to simulate on a quantum hardware. Resource estimation helps us assert the feasibility of an approach with regards to device capabilities (simulator or QPU), or compare it to alternatives, including what is considered state-of-the-art.\n", + "With this set up in mind, we can leverage Tangelo to estimate the resources required and the cost of this experiment. Later in the document, we show how by leveraging Tangelo and choosing smarter strategies to build an ansazte, one could turn a seemingly intractable problem into one easy to simulate on a quantum hardware. Resource estimation helps us assert the feasibility of an approach with regards to device capabilities (simulator or QPU), or compare it to alternatives, including what is considered state-of-the-art.\n", "\n", "Resource estimation is important, as quantum computing is still a nascent field and the current quantum computers have modest capabilities (limited amount of qubits, low gate fidelity, coherence time, etc. It can be one of the drivers of our exploration, and help us identify the most appropriate approaches in our experiments, as well as their bottlenecks, where impactful breakthroughs could make a difference.\n", "\n", @@ -251,7 +251,7 @@ } ], "source": [ - "from qsdk.algorithms.variational import BuiltInAnsatze, VQESolver\n", + "from tangelo.algorithms.variational import BuiltInAnsatze, VQESolver\n", "\n", "# VQE-UCCSD heads-on approach.\n", "vqe_options = {\"molecule\": mol, \"ansatz\": BuiltInAnsatze.UCCSD, \"qubit_mapping\": \"jw\"}\n", @@ -308,7 +308,7 @@ } ], "source": [ - "from qsdk.problem_decomposition.dmet.dmet_problem_decomposition import DMETProblemDecomposition, Localization\n", + "from tangelo.problem_decomposition.dmet.dmet_problem_decomposition import DMETProblemDecomposition, Localization\n", "\n", "# DMET-VQE, 5 fragments of size 2 atoms each\n", "dmet_options = {\"molecule\": mol, \"verbose\": False,\n", @@ -483,8 +483,8 @@ } ], "source": [ - "from qsdk.toolboxes.operators import FermionOperator, QubitOperator\n", - "from qsdk.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping\n", + "from tangelo.toolboxes.operators import FermionOperator, QubitOperator\n", + "from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping\n", " \n", "# Find all the measurement bases that are needed to compute the RDMs.\n", "# Accumulate them in a QubitOperator object to manipulate them afterwards.\n", @@ -548,7 +548,7 @@ } ], "source": [ - "from qsdk.toolboxes.measurements import group_qwc\n", + "from tangelo.toolboxes.measurements import group_qwc\n", "\n", "qwc_map = group_qwc(qubit_op_rdm, seed=0)\n", "print(f\"Only execute {len(qwc_map)} circuits, instead of {len(qubit_op_rdm.terms)}.\\n\")\n", @@ -602,8 +602,8 @@ } ], "source": [ - "from qsdk.backendbuddy import Circuit, Gate\n", - "from qsdk.backendbuddy.helpers.circuits.measurement_basis import measurement_basis_gates\n", + "from tangelo.backendbuddy import Circuit, Gate\n", + "from tangelo.backendbuddy.helpers.circuits.measurement_basis import measurement_basis_gates\n", "\n", "# Creation of XX, XZ, ZX, ZZ and YY circuits.\n", "# This is done by appending relevant gates to the quantum circuit representing the quantum state.\n", @@ -653,7 +653,7 @@ } ], "source": [ - "from qsdk.toolboxes.measurements.estimate_measurements import get_measurement_estimate\n", + "from tangelo.toolboxes.measurements.estimate_measurements import get_measurement_estimate\n", "\n", "measurements = {k: get_measurement_estimate(v, digits=2) for k,v in qwc_map.items()}\n", "pprint(measurements)" @@ -730,7 +730,7 @@ "os.environ['QEMIST_AUTH_TOKEN'] = \"your_qemist_authentication_token\"\n", "\n", "# Estimate, submit and get the results of your job / quantum task through our wrappers\n", - "from qsdk.backendbuddy.qpu_connection import job_submit, job_status, job_cancel, job_result, job_estimate\n", + "from tangelo.backendbuddy.qpu_connection import job_submit, job_status, job_cancel, job_result, job_estimate\n", "\n", "circuit_YY = quantum_circuit[((0, \"Y\"), (1, \"Y\"))]\n", "\n", @@ -751,7 +751,7 @@ "source": [ "### Using a cloud API and format conversion \n", "\n", - "The utility functions in `qsdk.backendbuddy` allow us to convert our generic `Circuit` objects into a variety of formats supported by other open-source packages and services, such as Amazon's Braket and Microsoft's Azure Quantum.\n", + "The utility functions in `tangelo.backendbuddy` allow us to convert our generic `Circuit` objects into a variety of formats supported by other open-source packages and services, such as Amazon's Braket and Microsoft's Azure Quantum.\n", "\n", "You can thus convert a `Circuit` object into the desired format and use the API of those services directly in order to reach a QPU or an online simulator if you wish to do so. The example below shows how to convert a `Circuit` object into the Braket format. Provided that you have a Braket account, the submission process is pretty straightforward, as demonstrated by the [documentation](https://github.com/aws/amazon-braket-sdk-python#usage)" ] @@ -778,7 +778,7 @@ } ], "source": [ - "from qsdk.backendbuddy.translator import translate_braket\n", + "from tangelo.backendbuddy.translator import translate_braket\n", "\n", "braket_circuit = translate_braket(circuit_YY)\n", "print(braket_circuit)" @@ -809,7 +809,7 @@ } ], "source": [ - "from qsdk.backendbuddy.translator import translate_cirq\n", + "from tangelo.backendbuddy.translator import translate_cirq\n", "\n", "cirq_circuit = translate_cirq(circuit_YY)\n", "print(cirq_circuit)" @@ -823,7 +823,7 @@ "\n", "Since many open-source packages support noisy simulation and hardware providers put out some information about their devices, you could be interested in performing the noisy simulation of your quantum circuits. In particular, this could help you get an idea of the performance of your algorithm on a target device or get a sense of the performance a device would require for your algorithm to return an answer within the desired accuracy, without requiring access to a QPU.\n", "\n", - "We provide a general interface giving you access to several simulator backends in order to facilitate the simulation of such circuits. You are free to use the \"translate\" functions of `qsdk` in order to use the API provided by your favorite open-source package directly if you'd like, as this offers finer control and maybe more features. \n", + "We provide a general interface giving you access to several simulator backends in order to facilitate the simulation of such circuits. You are free to use the \"translate\" functions of `tangelo` in order to use the API provided by your favorite open-source package directly if you'd like, as this offers finer control and maybe more features. \n", "\n", "Below, an example using our generic `NoiseModel` object and specifying the backend when calling `simulate`. Here we show an example applying a depolarization channel to specific gates, each with a given probability." ] @@ -836,8 +836,8 @@ }, "outputs": [], "source": [ - "from qsdk.backendbuddy import Simulator\n", - "from qsdk.backendbuddy.noisy_simulation import NoiseModel\n", + "from tangelo.backendbuddy import Simulator\n", + "from tangelo.backendbuddy.noisy_simulation import NoiseModel\n", "\n", "nmp = NoiseModel()\n", "nmp.add_quantum_error(\"CNOT\", \"depol\", 0.01)\n", @@ -1110,7 +1110,7 @@ "### Error mitigation \n", "\n", "The next step in our post-processing is focused on error mitigation. Due to noise, the hardware produces a mixed state, which reduces the accuracy of our observables. The ultimate tool against noise is error correction.\n", - "However, employing error correction is prohibitively expensive in terms of the quantum resources required and out of reach of near-term quantum hardware (hence the \"Noisy\" in NISQ). Although error correction is not currently available, we can still utilize clever ideas and leverage the known symmetries of the input problem to post-process the raw results coming form the hardware to mitigate the noise to some extent. qSDK aims to provide a collection of noise mitigation techniques.\n", + "However, employing error correction is prohibitively expensive in terms of the quantum resources required and out of reach of near-term quantum hardware (hence the \"Noisy\" in NISQ). Although error correction is not currently available, we can still utilize clever ideas and leverage the known symmetries of the input problem to post-process the raw results coming form the hardware to mitigate the noise to some extent. Tangelo aims to provide a collection of noise mitigation techniques.\n", "\n", "As an error-mitigation strategy in our DMET experiment, we use a density matrix purification technique based on McWeeny's purification method [[Truflandier et al., 2016](https://doi.org/10.1063/1.4943213)] to purify our noisy state to the dominant eigenvector. This is an iterative method which imposes the idempotency condition according to:\n", "\n", @@ -1138,15 +1138,15 @@ "name": "stderr", "output_type": "stream", "text": [ - "/home/valentin/Desktop/virtualenvs/qsdk_oct21/lib/python3.8/site-packages/qsdk/toolboxes/post_processing/mc_weeny_rdm_purification.py:68: ComplexWarning: Casting complex values to real discards the imaginary part\n", + "/home/valentin/Desktop/virtualenvs/tangelo_oct21/lib/python3.8/site-packages/tangelo/toolboxes/post_processing/mc_weeny_rdm_purification.py:68: ComplexWarning: Casting complex values to real discards the imaginary part\n", " rdm1_np_temp[i, j] += D_matrix2_final[i, j, k, k]\n", - "/home/valentin/Desktop/virtualenvs/qsdk_oct21/lib/python3.8/site-packages/qsdk/toolboxes/post_processing/mc_weeny_rdm_purification.py:74: ComplexWarning: Casting complex values to real discards the imaginary part\n", + "/home/valentin/Desktop/virtualenvs/tangelo_oct21/lib/python3.8/site-packages/tangelo/toolboxes/post_processing/mc_weeny_rdm_purification.py:74: ComplexWarning: Casting complex values to real discards the imaginary part\n", " rdm2_np[i//2, j//2, k//2, l//2] += D_matrix2_final[i, j, k, l]\n" ] } ], "source": [ - "from qsdk.toolboxes.post_processing.mc_weeny_rdm_purification import mcweeny_purify_2rdm\n", + "from tangelo.toolboxes.post_processing.mc_weeny_rdm_purification import mcweeny_purify_2rdm\n", "\n", "onerdm_spinsum, twordm_spinsum = mcweeny_purify_2rdm(twordm, conv=1e-2)\n", "e_pure_fragment = compute_electronic_fragment_energy(fragment, onerdm_spinsum, twordm_spinsum) + core_constant\n", @@ -1180,7 +1180,7 @@ } ], "source": [ - "from qsdk.toolboxes.post_processing.bootstrapping import get_resampled_frequencies\n", + "from tangelo.toolboxes.post_processing.bootstrapping import get_resampled_frequencies\n", "\n", "# Bootstrap method.\n", "fragment_energies = list()\n", @@ -1225,7 +1225,7 @@ "source": [ "## Closing words \n", "\n", - "This notebook showed how qSDK can assist us in implementing end-to-end workflows, which can lead to successful hardware experiments and peer-reviewed publications in the field.\n", + "This notebook showed how Tangelo can assist us in implementing end-to-end workflows, which can lead to successful hardware experiments and peer-reviewed publications in the field.\n", "\n", "On the one hand, it highlights how problem decomposition can make larger molecular systems amenable to NISQ devices, in combination with pre- and post-processing methods aiming at reducing resource requirements or improving accuracy. Such approaches may play an essential role in applying quantum computers to the study of larger, industrially relevant, chemical systems.\n", "\n", @@ -1257,9 +1257,9 @@ "hash": "89fcd33a61ce1c8ae91bdb4eff5eb93c6c13a9486366ee1fefd22c672d14276a" }, "kernelspec": { - "display_name": "qsdk_oct21", + "display_name": "tangelo_oct21", "language": "python", - "name": "qsdk_oct21" + "name": "tangelo_oct21" }, "language_info": { "codemirror_mode": { diff --git a/examples/problem_decomposition_oniom.ipynb b/examples/problem_decomposition_oniom.ipynb index 44868d605..ca5b842de 100755 --- a/examples/problem_decomposition_oniom.ipynb +++ b/examples/problem_decomposition_oniom.ipynb @@ -75,9 +75,9 @@ "source": [ "import math\n", "\n", - "from qsdk.problem_decomposition.oniom.oniom_problem_decomposition import ONIOMProblemDecomposition\n", - "from qsdk.problem_decomposition.oniom._helpers.helper_classes import Fragment, Link\n", - "from qsdk.algorithms import BuiltInAnsatze as Ansatze" + "from tangelo.problem_decomposition.oniom.oniom_problem_decomposition import ONIOMProblemDecomposition\n", + "from tangelo.problem_decomposition.oniom._helpers.helper_classes import Fragment, Link\n", + "from tangelo.algorithms import BuiltInAnsatze as Ansatze" ] }, { @@ -244,9 +244,9 @@ ], "metadata": { "kernelspec": { - "display_name": "qsdk_sept_21", + "display_name": "tangelo_sept_21", "language": "python", - "name": "qsdk_sept_21" + "name": "tangelo_sept_21" }, "language_info": { "codemirror_mode": { diff --git a/examples/qemist_cloud_hardware_experiments_braket.ipynb b/examples/qemist_cloud_hardware_experiments_braket.ipynb index 23f7eb1c0..a68d6f9bf 100755 --- a/examples/qemist_cloud_hardware_experiments_braket.ipynb +++ b/examples/qemist_cloud_hardware_experiments_braket.ipynb @@ -4,18 +4,18 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Submitting quantum hardware experiments with qSDK and QEMIST Cloud" + "# Submitting quantum hardware experiments with Tangelo and QEMIST Cloud" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "This short tutorial shows how users can achieve that by simply installing both the qSDK and QEMIST Cloud's client library, and access all the backends available in [Amazon Braket](https://aws.amazon.com/braket/).\n", + "This short tutorial shows how users can achieve that by simply installing both the Tangelo and QEMIST Cloud's client library, and access all the backends available in [Amazon Braket](https://aws.amazon.com/braket/).\n", "\n", - "qSDK users can express quantum circuits in various formats, which can be then submitted to one of the quantum cloud services providers using their account credentials and the API.\n", + "Tangelo users can express quantum circuits in various formats, which can be then submitted to one of the quantum cloud services providers using their account credentials and the API.\n", "\n", - "This short tutorial shows how users can access all the backends available in Amazon Braket by simply installing both the qSDK and QEMIST Cloud's client library, then running quantum hardware experiments using their QEMIST Cloud account credentials and credits." + "This short tutorial shows how users can access all the backends available in Amazon Braket by simply installing both the Tangelo and QEMIST Cloud's client library, then running quantum hardware experiments using their QEMIST Cloud account credentials and credits." ] }, { @@ -30,7 +30,7 @@ "metadata": {}, "source": [ "In order to succesfully submit an experiment, you will first need to:\n", - "- install `qSDK`\n", + "- install `Tangelo`\n", "- install `qemist-client` (QEMIST client library)\n", "\n", "Once you have installed the two required packages, set up both environment variables `QEMIST_PROJECT_ID` and `QEMIST_AUTH_TOKEN` in your local environment. You may set these variables in your OS / terminal, or provide them in your script using the `os` module, as below:" @@ -70,7 +70,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "First, we define a quantum circuit using the API provided by qSDK. For the purpose of this tutorial, we here explicitly define a short circuit that simply prepares a Bell state. However, this feature works with any quantum circuit expressed in the qSDK format, including those you may have obtained from a complicated custom workflow expressed with qSDK." + "First, we define a quantum circuit using the API provided by Tangelo. For the purpose of this tutorial, we here explicitly define a short circuit that simply prepares a Bell state. However, this feature works with any quantum circuit expressed in the Tangelo format, including those you may have obtained from a complicated custom workflow expressed with Tangelo." ] }, { @@ -91,7 +91,7 @@ } ], "source": [ - "from qsdk.backendbuddy import Gate, Circuit\n", + "from tangelo.backendbuddy import Gate, Circuit\n", "\n", "circuit = Circuit([Gate(\"H\", 0), Gate(\"CNOT\", 1, control=0)])\n", "print(circuit)" @@ -101,7 +101,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In order to submit a quantum job and run this circuit on one of the available devices, we import a few convenience functions from the qSDK, which rely on the QEMIST client library. We illustrate their usage in the rest of the notebook." + "In order to submit a quantum job and run this circuit on one of the available devices, we import a few convenience functions from the Tangelo, which rely on the QEMIST client library. We illustrate their usage in the rest of the notebook." ] }, { @@ -110,7 +110,7 @@ "metadata": {}, "outputs": [], "source": [ - "from qsdk.backendbuddy.qpu_connection import job_submit, job_status, job_cancel, job_result, job_estimate" + "from tangelo.backendbuddy.qpu_connection import job_submit, job_status, job_cancel, job_result, job_estimate" ] }, { @@ -304,9 +304,9 @@ ], "metadata": { "kernelspec": { - "display_name": "qsdk_aug_21", + "display_name": "tangelo_aug_21", "language": "python", - "name": "qsdk_aug_21" + "name": "tangelo_aug_21" }, "language_info": { "codemirror_mode": { diff --git a/examples/test_notebooks.py b/examples/test_notebooks.py index 7745dc73e..703a76787 100755 --- a/examples/test_notebooks.py +++ b/examples/test_notebooks.py @@ -9,9 +9,9 @@ def run_notebook_as_test(notebook_path): subprocess.run(['jupyter', 'nbconvert', '--to', 'script', notebook_path]) script_path = os.path.splitext(notebook_path)[0] + '.py' subprocess.run(['chmod', '+x', script_path]) - subprocess.run([script_path], check=True) - except Exception as err: - raise RuntimeError(err.args) + subprocess.check_output([script_path]) + except subprocess.CalledProcessError as e: + raise e class TestNotebooks(unittest.TestCase): diff --git a/examples/vqe.ipynb b/examples/vqe.ipynb index e65d6348f..edb4884fa 100755 --- a/examples/vqe.ipynb +++ b/examples/vqe.ipynb @@ -4,11 +4,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# VQE with qSDK\n", + "# VQE with Tangelo\n", "\n", - "The qSDK Python package provides various toolboxes, which can be leveraged to build quantum chemistry workflows relying on quantum computing. One example of such a workflow is the Variational Quantum Eigensolver (VQE). We provide an implementation of VQE that supports several options, and may provide valuable help in your research and applications.\n", + "The Tangelo Python package provides various toolboxes, which can be leveraged to build quantum chemistry workflows relying on quantum computing. One example of such a workflow is the Variational Quantum Eigensolver (VQE). We provide an implementation of VQE that supports several options, and may provide valuable help in your research and applications.\n", "\n", - "This notebook assumes that you already have installed qSDK in your Python environment, or have updated your Python path so that the imports can be resolved. " + "This notebook assumes that you already have installed Tangelo in your Python environment, or have updated your Python path so that the imports can be resolved. " ] }, { @@ -85,7 +85,7 @@ "metadata": {}, "outputs": [], "source": [ - "from qsdk import SecondQuantizedMolecule\n", + "from tangelo import SecondQuantizedMolecule\n", "\n", "H2 = [('H', (0, 0, 0)),('H', (0, 0, 0.74137727))]\n", "mol_H2 = SecondQuantizedMolecule(H2, q=0, spin=0, basis=\"sto-3g\")" @@ -123,7 +123,7 @@ "{'molecule': SecondQuantizedMolecule(xyz=[('H', (0, 0, 0)), ('H', (0, 0, 0.74137727))], q=0, spin=0, n_atoms=2, n_electrons=2, n_min_orbitals=2, basis='sto-3g', mf_energy=-1.1166856303994788, mo_energies=array([-0.5779842, 0.6697221]), mo_occ=array([2., 0.]), mean_field=, n_mos=2, n_sos=4, active_occupied=[0], frozen_occupied=[], active_virtual=[1], frozen_virtual=[]),\n", " 'qubit_mapping': 'jw',\n", " 'ansatz': ,\n", - " 'optimizer': >,\n", + " 'optimizer': >,\n", " 'initial_var_params': None,\n", " 'backend_options': {'target': None, 'n_shots': None, 'noise_model': None},\n", " 'penalty_terms': None,\n", @@ -146,7 +146,7 @@ } ], "source": [ - "from qsdk.algorithms import VQESolver\n", + "from tangelo.algorithms import VQESolver\n", "\n", "vqe_options = {\"molecule\": mol_H2}\n", "vqe_solver = VQESolver(vqe_options)\n", @@ -185,8 +185,8 @@ "text/plain": [ "{'molecule': SecondQuantizedMolecule(xyz=[('H', (0, 0, 0)), ('H', (0, 0, 0.74137727))], q=0, spin=0, n_atoms=2, n_electrons=2, n_min_orbitals=2, basis='sto-3g', mf_energy=-1.1166856303994788, mo_energies=array([-0.5779842, 0.6697221]), mo_occ=array([2., 0.]), mean_field=, n_mos=2, n_sos=4, active_occupied=[0], frozen_occupied=[], active_virtual=[1], frozen_virtual=[]),\n", " 'qubit_mapping': 'jw',\n", - " 'ansatz': ,\n", - " 'optimizer': >,\n", + " 'ansatz': ,\n", + " 'optimizer': >,\n", " 'initial_var_params': [2e-05, 0.036324160602554285],\n", " 'backend_options': {'target': None, 'n_shots': None, 'noise_model': None},\n", " 'penalty_terms': None,\n", @@ -215,7 +215,7 @@ " ,\n", " ,\n", " },\n", - " 'backend': }" + " 'backend': }" ] }, "execution_count": 3, @@ -490,7 +490,7 @@ } ], "source": [ - "from qsdk.algorithms import FCISolver, CCSDSolver\n", + "from tangelo.algorithms import FCISolver, CCSDSolver\n", "\n", "fci_solver = FCISolver(mol_H2)\n", "energy_fci = fci_solver.simulate()\n", @@ -620,7 +620,7 @@ "\n", "The `VQESolver` class supports a `frozen_orbitals` option, allowing a user to pass either a list of indices (integers) referring to the orbitals to freeze, or a single integer indicating that all orbitals up to that index (excluded) must be frozen. For instance, just passing `3` would be equivalent to passing `[0, 1, 2]`.\n", "\n", - "Internally, this information is parsed and allows us to track which orbitals are active or frozen and occupied or virtual, through an object carrying molecular data which is an attribute of the `VQESolver`. By default, the option \"frozen_core\" is selected. If this string is detected, the function `qsdk.toolboxes.molecular_computation.frozen_orbitals.get_frozen_core` is called. It takes a `Molecule` or a `SecondQuantizedMolecule` object and returns an integer corresponding to the number of low-energy orbitals. In short, no orbital for each period 1 element, one orbital for each period 2 element and five orbitals for each period 3 element are summed up and frozen in the subsequent calculation. It is important to emphasize that this option does nothing if a custom qubit Hamiltonian is provided instead of a molecule.\n", + "Internally, this information is parsed and allows us to track which orbitals are active or frozen and occupied or virtual, through an object carrying molecular data which is an attribute of the `VQESolver`. By default, the option \"frozen_core\" is selected. If this string is detected, the function `tangelo.toolboxes.molecular_computation.frozen_orbitals.get_frozen_core` is called. It takes a `Molecule` or a `SecondQuantizedMolecule` object and returns an integer corresponding to the number of low-energy orbitals. In short, no orbital for each period 1 element, one orbital for each period 2 element and five orbitals for each period 3 element are summed up and frozen in the subsequent calculation. It is important to emphasize that this option does nothing if a custom qubit Hamiltonian is provided instead of a molecule.\n", "\n", "Let's have a look at this quickly, taking a H4 molecule in sto-3g basis as an example. We can compare the results provided by VQE to our classical CCSD solver, which also support frozen orbitals with a slightly different interface." ] @@ -771,13 +771,13 @@ "name": "stderr", "output_type": "stream", "text": [ - "/media/sf_QEMIST_qSDK/qsdk/qsdk/toolboxes/qubit_mappings/statevector_mapping.py:53: RuntimeWarning: Symmetry-conserving Bravyi-Kitaev enforces all spin-up followed by all spin-down ordering.\n", + "/media/sf_QEMIST_Tangelo/tangelo/tangelo/toolboxes/qubit_mappings/statevector_mapping.py:53: RuntimeWarning: Symmetry-conserving Bravyi-Kitaev enforces all spin-up followed by all spin-down ordering.\n", " warnings.warn(\"Symmetry-conserving Bravyi-Kitaev enforces all spin-up followed by all spin-down ordering.\", RuntimeWarning)\n" ] } ], "source": [ - "from qsdk.algorithms import BuiltInAnsatze as Ansatze\n", + "from tangelo.algorithms import BuiltInAnsatze as Ansatze\n", "\n", "# VQE-UCCSD on H2 with different qubit mappings\n", "for qm in ['jw', 'bk', 'scbk']:\n", @@ -889,7 +889,7 @@ "\n", "The `VQESolver` class relies on the `backendbuddy` submodule in order to simulate your quantum circuits. \n", "\n", - "For more information about the `backend` attribute of your `VQE_Solver` object, of type `qsdk.backendbuddy.Simulator`, we recommend you check the `backendbuddy` tutorial notebooks.\n", + "For more information about the `backend` attribute of your `VQE_Solver` object, of type `tangelo.backendbuddy.Simulator`, we recommend you check the `backendbuddy` tutorial notebooks.\n", "\n", "This object allows you to pick from a number of different classical simulators, with significantly different performance, some supporting noisy simulation. The parameters `n_shots` introduces statistical noise in the computation, which correlates to accuracy of energy estimation, while `noise_model` enables us to model noise in the circuit to an extent. These parameters are provided to enable the modeling of experiments that may be closer to the behaviour of quantum hardware.\n", "\n", @@ -1003,9 +1003,9 @@ ], "metadata": { "kernelspec": { - "display_name": "qSDK", + "display_name": "Tangelo", "language": "python", - "name": "qsdk" + "name": "tangelo" }, "language_info": { "codemirror_mode": { diff --git a/examples/vqe_custom_ansatz_hamiltonian.ipynb b/examples/vqe_custom_ansatz_hamiltonian.ipynb index ebad936ce..b78f4a47b 100755 --- a/examples/vqe_custom_ansatz_hamiltonian.ipynb +++ b/examples/vqe_custom_ansatz_hamiltonian.ipynb @@ -4,9 +4,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# qSDK VQE: Custom Ansatz and qubit Hamiltonian Tutorial\n", + "# Tangelo VQE: Custom Ansatz and qubit Hamiltonian Tutorial\n", "\n", - "The `qSDK` comes packaged with an implementation of several standard ansatz circuits for the user to take advantage of. In this tutorial, we'll explore how you can incorporate the built in `VQESolver` into your own workflow, by introducing a user-defined custom ansatz circuit and/or a qubit Hamiltonian. We'll base our work here on the `VQESolver` class, and take advantage of tools readily available through the `qSDK`." + "The `Tangelo` comes packaged with an implementation of several standard ansatz circuits for the user to take advantage of. In this tutorial, we'll explore how you can incorporate the built in `VQESolver` into your own workflow, by introducing a user-defined custom ansatz circuit and/or a qubit Hamiltonian. We'll base our work here on the `VQESolver` class, and take advantage of tools readily available through the `Tangelo`." ] }, { @@ -17,12 +17,12 @@ "source": [ "import numpy as np\n", "\n", - "from qsdk.algorithms import VQESolver, FCISolver\n", - "from qsdk import SecondQuantizedMolecule\n", - "from qsdk.toolboxes.ansatz_generator.ansatz import Ansatz\n", - "from qsdk.toolboxes.qubit_mappings.statevector_mapping import get_reference_circuit\n", - "from qsdk.toolboxes.qubit_mappings.mapping_transform import get_qubit_number, fermion_to_qubit_mapping\n", - "from qsdk.backendbuddy import Circuit, Gate" + "from tangelo.algorithms import VQESolver, FCISolver\n", + "from tangelo import SecondQuantizedMolecule\n", + "from tangelo.toolboxes.ansatz_generator.ansatz import Ansatz\n", + "from tangelo.toolboxes.qubit_mappings.statevector_mapping import get_reference_circuit\n", + "from tangelo.toolboxes.qubit_mappings.mapping_transform import get_qubit_number, fermion_to_qubit_mapping\n", + "from tangelo.backendbuddy import Circuit, Gate" ] }, { @@ -176,7 +176,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Next, we'll implement `update_var_params`, where the circuit is updated with a new batch of variational parameters. The `qsdk.backendbuddy` Circuit class keeps a record of the variational gates in the circuit, making this update very straightforward, and avoids having to rebuild the circuit from scratch. All variational gates in the circuit are updated as per the `var_params` argument." + "Next, we'll implement `update_var_params`, where the circuit is updated with a new batch of variational parameters. The `tangelo.backendbuddy` Circuit class keeps a record of the variational gates in the circuit, making this update very straightforward, and avoids having to rebuild the circuit from scratch. All variational gates in the circuit are updated as per the `var_params` argument." ] }, { @@ -645,15 +645,15 @@ "source": [ "# Conclusion\n", "\n", - "In this tutorial, we've seen how to implement a custom ansatz circuit for VQE using the tools from `qSDK` and how to combine it with a built-in or a custom qubit Hamiltonian. Hopefully, this gives some impression of how this platform is designed to help users construct their own workflows easily, focusing on the specific issues they are interested in studying without the distraction of building the supporting framework from scratch. " + "In this tutorial, we've seen how to implement a custom ansatz circuit for VQE using the tools from `Tangelo` and how to combine it with a built-in or a custom qubit Hamiltonian. Hopefully, this gives some impression of how this platform is designed to help users construct their own workflows easily, focusing on the specific issues they are interested in studying without the distraction of building the supporting framework from scratch. " ] } ], "metadata": { "kernelspec": { - "display_name": "qSDK", + "display_name": "Tangelo", "language": "python", - "name": "qsdk" + "name": "tangelo" }, "language_info": { "codemirror_mode": { diff --git a/qsdk/backendbuddy/qpu_connection/honeywell_connection.py b/qsdk/backendbuddy/qpu_connection/honeywell_connection.py deleted file mode 100644 index 164c92fad..000000000 --- a/qsdk/backendbuddy/qpu_connection/honeywell_connection.py +++ /dev/null @@ -1,194 +0,0 @@ -# Copyright 2021 1QB Information Technologies Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Python wrappers around Honeywell REST API, to facilitate job submission, -result retrieval and post-processing. - -Using Honeywell services require logins to their portal: -https://um.qapi.honeywell.com/index.html -Users are expected to set the environment variables HONEYWELL_EMAIL, -HONEYWELL_PASSWORD with their credentials. - -The portal above provides access to a dashboard, which is better suited for job -monitoring experiments. -""" - -import os -import re -import json -import time -import pprint -import requests as rq -from collections import Counter - -from qsdk.backendbuddy.qpu_connection.qpu_connection import QpuConnection - - -class HoneywellConnection(QpuConnection): - """Wrapper about the Honeywell REST API, to facilitate job submission and - automated post-processing of results. - """ - - def __init__(self): - self.endpoint = "https://qapi.honeywell.com" + "/v1/" # Update endpoint or version number here if needed - self.id_token, self.refresh_token = None, None - self._login() - - @property - def header(self): - """ Produce the header for REST requests """ - return {"Content-Type": "application/json", "Authorization": self.id_token} - - def _login(self): - """Use Honeywell logins (email, password) to retrieve the id token to be - used for request with their REST API. Assumes users have set - HONEYWELL_EMAIL and HONEYWELL_PASSWORD in their environment variables. - """ - - honeywell_email, honeywell_password = os.getenv("HONEYWELL_EMAIL", None), os.getenv("HONEYWELL_PASSWORD", None) - if not (honeywell_email and honeywell_password): - raise RuntimeError(f"Please set these environment variables: HONEYWELL_EMAIL, HONEYWELL_PASSWORD") - - login_body = {"email": honeywell_email, "password": honeywell_password} - login_response = rq.post(self.endpoint+"/login", headers=self.header, data=json.dumps(login_body)) - login_response = json.loads(login_response.text) - - self._catch_request_error(login_response) - self.id_token, self.refresh_token = login_response["id-token"], login_response["refresh-token"] - - def _catch_request_error(self, return_dict): - """Use the dictionary returned from a REST request to check for errors - at runtime, and catch them. - """ - if "error" in return_dict: - pprint.pprint(return_dict) - raise RuntimeError(f"Error returned by Honeywell API :\n{return_dict['error']}") - - def get_devices(self): - """Return dictionary of available devices to the user, as well as some - useful information about them. - """ - - device_list_response = rq.get(self.endpoint + "/machine?config=true", headers=self.header) - available_devices = json.loads(device_list_response.text) - self._catch_request_error(available_devices) - - # Append useful information regarding said devices (number of qubits, current status...) - device_info = dict() - for device in available_devices: - device_name = device.pop("name") if isinstance(device, dict) else device - device_state = rq.get(self.endpoint + "/machine/" + device_name, headers=self.header) - device_state = json.loads(device_state.text) - self._catch_request_error(device_state) - device_info[device_name] = {**device, **device_state} - return device_info - - def job_submit(self, machine_name, qasm_circuit, n_shots, job_name, **job_specs): - """Submit job, return job ID. - - Args: - machine_name (str): name of the target device. - qasm_circuit (str): openqasm 2.0 string representing the quantum - circuit. - n_shots (int): number of shots. - job_name (str): name to make the job more identifiable. - **job_specs: extra arguments such as `max_cost` or `options` in the - code below. - - Returns: - str: alphanumeric character string representing the job id. - """ - - # Honeywell does not support openqasm comments: remove them before submission - qasm_circuit = re.sub(r'//(.*)\n', '', qasm_circuit) - - body = {"machine": machine_name, - "name": job_name, - "count": n_shots, - "program": qasm_circuit, - "language": "OPENQASM 2.0", - "notify": True, - "max-cost": job_specs.get("max_cost", 100), - "options": job_specs.get("options", ["no-opt"]) - } - - job_request = rq.post(self.endpoint + "/job/", headers=self.header, data=json.dumps(body)) - job_request = json.loads(job_request.text) - - self._catch_request_error(job_request) - print(f"Job ID :: {job_request['job']} \t status :: {job_request['status']}") - return job_request['job'] - - def job_get_info(self, job_id): - """Returns information about the job corresponding to the input job id. - - Args: - job_id (str): alphanumeric character string representing the job id. - - Returns: - dict: status response from the Honeywell REST API. - """ - - option = "?websocket=true" - job_status = rq.get(self.endpoint + "/job/" + job_id + option, headers=self.header) - job_status = json.loads(job_status.text) - - self._catch_request_error(job_status) - print(f"Job {job_id} \t status :: {job_status['status']} {job_status.get('error', '')}") - return job_status - - def job_get_results(self, job_id): - """Blocking call querying the REST API at a given frequency, until job - results are available. - - Args: - job_id (str): alphanumeric character string representing the job id. - - Returns: - dict: status response from the Honeywell REST API. - """ - - # The only way to retrieve the results, see if a job submission was incorrect, etc, is to look at the job info - while True: - job_status = self.job_get_info(job_id) - if job_status['status'] == 'completed' and 'results' in job_status: - return job_status['results'] - # TODO: if we know the qubit order, we can return result in standard backendbuddy format later - # return job_status['results'] if raw else dict(Counter(job_status['results']['c'])) - # binary string keys of dictionary may need to be reversed. - elif job_status['status'] in {'queued', 'running'}: - time.sleep(10) - else: - raise RuntimeError(f'Unexpected job status :: \n {job_status}') - - def job_cancel(self, job_id): - """Blocking call querying the REST API at a given frequency, until job - results are available. - - Args: - str: alphanumeric character string representing the job id. - """ - job_cancel = rq.post(self.endpoint+"/job/"+job_id+"/cancel", headers=self.header, data={}) - job_cancel = json.loads(job_cancel.text) - - # The operation may fail if it is too late for the job to be canceled - # In that case, the error message is printed and the error is raised, but we don't follow up on that - try: - self._catch_request_error(job_cancel) - except RuntimeError: - pass - - time.sleep(3) - job_status = self.job_get_info(job_id) - return job_status diff --git a/qsdk/backendbuddy/qpu_connection/ionq_connection.py b/qsdk/backendbuddy/qpu_connection/ionq_connection.py deleted file mode 100644 index 5bfe7d42a..000000000 --- a/qsdk/backendbuddy/qpu_connection/ionq_connection.py +++ /dev/null @@ -1,185 +0,0 @@ -# Copyright 2021 1QB Information Technologies Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Python wrappers around IonQ REST API, to facilitate job submission, result -retrieval and post-processing - -Using IonQ services requires an API key. -Users are expected to set the environment variable IONQ_APIKEY with the value of -this token. - -Please check IonQ documentations to ensure what features and acceptable values -exist: -https://dewdrop.ionq.co/ -https://docs.ionq.co -""" - -import os -import json -import time -import pprint -import pandas as pd -import requests as rq - -from qsdk.backendbuddy.qpu_connection.qpu_connection import QpuConnection - - -class IonQConnection(QpuConnection): - """Wrapper about the IonQ REST API, to facilitate job submission and - automated post-processing of results. - """ - - def __init__(self): - self.endpoint = "https://api.ionq.co" + "/v0.1" # Update endpoint or version number here if needed - self.api_key = None - self._login() - - @property - def header(self): - """Produce the header for REST requests.""" - return {"Content-Type": "application/json", "Authorization": self.api_key} - - def _login(self): - """ - Retrieve the API key to be used for request with IonQ's REST API. - Assumes users have the environment variable IONQ_APIKEY set to the correct value. - """ - api_key = os.getenv("IONQ_APIKEY", None) - if not api_key: - raise RuntimeError(f"Please set these environment variables: IONQ_APIKEY") - self.api_key = f'apiKey {api_key}' - - # Verify API key by submitting a trivial request - try: - self.job_get_history() - except RuntimeError as err: - raise RuntimeError(f"{err}") - - def _catch_request_error(self, return_dict): - """Use the dictionary returned from a REST request to check for errors - at runtime, and catch them. - """ - if "error" in return_dict: - pprint.pprint(return_dict) - raise RuntimeError(f"Error returned by IonQ API :\n{return_dict['error']}") - - def _get_job_dataframe(self, job_history): - """Display main job info as pandas dataframe. Takes REST request answer - as input. - """ - - jl = job_history['jobs'] - jl_info = [(j['id'], j['status'], j['target']) for j in jl] - jobs_df = pd.DataFrame(jl_info, columns=['id', 'status', 'target']) - return jobs_df - - def job_submit(self, target_backend, ionq_circuit, n_shots, job_name, **job_specs): - """Submit job, return job ID. - - Args: - target_backend (str): name of target device. See IonQ documentation - for possible values. Current acceptable values are 'simulator' - and 'qpu'. - ionq_circuit (str): Circuit in a compatible format (json format - recommended). - n_shots (int): number of shots (ignored if target_backend is set to - `simulator`. - job_name (str): name to make the job more identifiable. - **job_specs: extra arguments such as `lang` in the code below; - `metadata` is not currently supported. - - Returns: - str: string representing the job id. - """ - - payload = {"target": target_backend, - "name": job_name, - "shots": n_shots, - "body": json.dumps(ionq_circuit), - "lang": job_specs.get('lang', 'json') - } - - job_request = rq.post(self.endpoint + '/jobs', headers=self.header, data=json.dumps(payload)) - return_dict = json.loads(job_request.text) - - self._catch_request_error(return_dict) - print(f"Job submission \tID :: {return_dict['id']} \t status :: {return_dict['status']}") - return return_dict['id'] - - def job_get_history(self): - """Returns information about the job corresponding to the input job id. - - Args: - job_id (str): alphanumeric character string representing the job id. - - Returns: - dict: status response from the REST API. - """ - - job_history = rq.get(self.endpoint + '/jobs', headers=self.header) - return_dict = json.loads(job_history.text) - - self._catch_request_error(return_dict) - return self._get_job_dataframe(return_dict) - - def job_get_info(self, job_id): - """Returns information about the job corresponding to the input job id. - - Args: - job_id (str): string representing the job id. - - Returns: - dict: status response from the REST API. - """ - - job_status = rq.get(self.endpoint + "/jobs/" + job_id, headers=self.header) - job_status = json.loads(job_status.text) - self._catch_request_error(job_status) - print(f"Job info \tID:: {job_id} \t status :: {job_status['status']} {job_status.get('error', '')}") - return job_status - - def job_get_results(self, job_id): - """Blocking call querying the REST API at a given frequency, until job - results are available. - - Args: - job_id (str): string representing the job id. - - Returns: - dict: status response from the REST API. - """ - - while True: - job_status = self.job_get_info(job_id) - if job_status['status'] == 'completed' and 'data' in job_status: - return job_status['data'] - elif job_status['status'] in {'ready', 'running', 'submitted'}: - time.sleep(10) - else: - raise RuntimeError(f'Unexpected job status :: \n {job_status}') - - def job_cancel(self, job_id): - """Cancel / delete a job from IonQ servers. - - Args: - job_id (str): string representing the job id. - - Returns: - dict: status response from the REST API. - """ - job_cancel = rq.delete(self.endpoint+"/jobs/"+job_id, headers=self.header) - job_cancel = json.loads(job_cancel.text) - self._catch_request_error(job_cancel) - print(f"Job cancel \tID :: {job_id} \t status :: {job_cancel['status']} {job_cancel.get('error', '')}") - return job_cancel diff --git a/qsdk/backendbuddy/qpu_connection/qpu_connection.py b/qsdk/backendbuddy/qpu_connection/qpu_connection.py deleted file mode 100644 index 61c9af24b..000000000 --- a/qsdk/backendbuddy/qpu_connection/qpu_connection.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright 2021 1QB Information Technologies Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Abstract parent class encapsulating basic features of compute services for -quantum circuit simulation. -""" - -import abc - - -class QpuConnection(abc.ABC): - """Abstract class encapsulating login/authentication setup and job - submission and management. - """ - - @abc.abstractmethod - def __init__(self): - pass - - @abc.abstractmethod - def job_submit(self): - """Submit a job to the compute services.""" - pass - - @abc.abstractmethod - def job_get_info(self, job_id): - """Retrieve information about a previously submitted job, through its - job id. - """ - pass - - @abc.abstractmethod - def job_get_results(self, job_id): - """Retrieve the results of previously submitted job, through its job id.""" - pass - - @abc.abstractmethod - def job_cancel(self, job_id): - """Attempt to cancel a previously submitted job, through its job id.""" - pass diff --git a/qsdk/backendbuddy/tests/test_connection_honeywell.py b/qsdk/backendbuddy/tests/test_connection_honeywell.py deleted file mode 100644 index 1c6f01ca2..000000000 --- a/qsdk/backendbuddy/tests/test_connection_honeywell.py +++ /dev/null @@ -1,88 +0,0 @@ -# Copyright 2021 1QB Information Technologies Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" - A test class to check that features related to the Honeywell API are behaving as expected. - Tests requiring actual interactions with the services have been skipped. -""" - -import unittest -import os - -from qsdk.backendbuddy import Gate, Circuit, translate_openqasm -from qsdk.backendbuddy.qpu_connection import HoneywellConnection - - -circ1 = Circuit([Gate("H", 0), Gate("CNOT", target=1, control=0)]) -circ1_qasm = translate_openqasm(circ1) - - -class TestHoneywellConnection(unittest.TestCase): - - @unittest.skip("We do not want to store login information for automated testing") - def test_init(self): - """ If the user has set environment variables HONEYWELL_EMAIL and HONEYWELL_PASSWORD to the correct login - information, this test should succeed. """ - HoneywellConnection() - - def test_init_fail(self): - """ If the user has not set environment variables HONEYWELL_EMAIL and HONEYWELL_PASSWORD to the correct login - information, this should fail and return an EnvironmentError. """ - tmp = os.getenv("HONEYWELL_EMAIL", 'empty') - os.environ['HONEYWELL_EMAIL'] = 'dummy_email' - self.assertRaises(RuntimeError, HoneywellConnection) - os.environ['HONEYWELL_EMAIL'] = tmp - - @unittest.skip("We do not want to store login information for automated testing") - def test_submit_job(self): - """ Submit a valid job to a API validation backend (if available) and retrieve results """ - - honeywell_api = HoneywellConnection() - devices = honeywell_api.get_devices() - print(f"{devices}") - validation_devices = [d for d in devices if d.endswith('APIVAL')] - print(validation_devices) - - if not validation_devices: - print("No `APIVAL` (validation) device currently available through Honeywell. Ending job submission test.") - return - - n_shots = 10 - job_id = honeywell_api.job_submit(validation_devices[0], circ1_qasm, n_shots, '1qbit_test_submit_job') - results = honeywell_api.job_get_results(job_id) - self.assertEqual(results, {'00': n_shots}) # Validation backend does not perform any simulation - - @unittest.skip("We do not want to store login information for automated testing") - def test_submit_job_incorrect_circuit(self): - """ Submit an incorrect job to a API validation backend (if available) and retrieve results """ - - circ_qasm = "#$%+" + translate_openqasm(circ1) - - honeywell_api = HoneywellConnection() - devices = honeywell_api.get_devices() - print(f"{devices}") - validation_devices = [d for d in devices if d.endswith('APIVAL')] - print(validation_devices) - - if not validation_devices: - print("No `APIVAL` (validation) device currently available through Honeywell. Ending job submission test.") - return - - n_shots = 10 - job_id = honeywell_api.job_submit(validation_devices[0], circ_qasm, n_shots, '1qbit_test_submit_job') - self.assertRaises(RuntimeError, honeywell_api.job_get_results, job_id) - - -if __name__ == "__main__": - unittest.main() diff --git a/qsdk/backendbuddy/tests/test_connection_ionq.py b/qsdk/backendbuddy/tests/test_connection_ionq.py deleted file mode 100644 index 0b3b30c55..000000000 --- a/qsdk/backendbuddy/tests/test_connection_ionq.py +++ /dev/null @@ -1,101 +0,0 @@ -# Copyright 2021 1QB Information Technologies Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" - A test class to check that features related to the Honeywell API are behaving as expected. - Tests requiring actual interactions with the services have been skipped. -""" - -import unittest -import os -import pprint -import time - -from qsdk.backendbuddy import Gate, Circuit, translator -from qsdk.backendbuddy.qpu_connection import IonQConnection - -circ1 = Circuit([Gate("H", 0), Gate("CNOT", target=1, control=0)]) -json_circ1 = translator.translate_json_ionq(circ1) -res_simulator_circ1 = {'histogram': {'0': 0.5, '3': 0.5}} - - -def assert_freq_dict_almost_equal(d1, d2, atol): - """ Utility function to check whether two frequency dictionaries are almost equal, for arbitrary tolerance """ - if d1.keys() != d2.keys(): - raise AssertionError("Dictionary keys differ. Frequency dictionaries are not almost equal.\n" - f"d1 keys: {d1.keys()} \nd2 keys: {d2.keys()}") - else: - for k in d1.keys(): - if abs(d1[k] - d2[k]) > atol: - raise AssertionError(f"Frequency {k}, difference above tolerance {atol}: {d1[k]} != {d2[k]}") - - -class TestIonQConnection(unittest.TestCase): - - @unittest.skip("We do not want to store login information for automated testing") - def test_init(self): - """ If user has set environment variables IONQ_APIKEY to the correct value, this should succeed. - Implicitly makes a call to IonQConnection.job_get_history in order to validate the apiKey. """ - IonQConnection() - - def test_init_fail(self): - """ If user has not set environment variables IONQ_APIKEY to the correct value, this should - return a RuntimeError. """ - tmp = os.getenv("IONQ_APIKEY", '') - os.environ['IONQ_APIKEY'] = 'invalid_apikey' - self.assertRaises(RuntimeError, IonQConnection) - os.environ['IONQ_APIKEY'] = '' - self.assertRaises(RuntimeError, IonQConnection) - os.environ['IONQ_APIKEY'] = tmp - - @unittest.skip("We do not want to store login information for automated testing") - def test_submit_job_simulator(self): - """ Submit a valid job to a API validation backend (simulator) and retrieve results """ - - ionq_api = IonQConnection() - - job_id = ionq_api.job_submit('simulator', json_circ1, 100, 'test_simulator_json_job') - job_results = ionq_api.job_get_results(job_id) - pprint.pprint(job_results) - - assert_freq_dict_almost_equal(job_results['histogram'], res_simulator_circ1['histogram'], 1e-7) - - @unittest.skip("We do not want to store login information for automated testing") - def test_submit_job_simulator_fail(self): - """ Submit invalid job, watch the World burn """ - - ionq_api = IonQConnection() - - json_incorrect_circ = {'circuit': [{'gate': 'potato', 'target': -1}], 'qubits': 1} - job_id = ionq_api.job_submit('simulator', json_incorrect_circ, 100, 'test_simulator_json_job') - time.sleep(5) - job_info = ionq_api.job_get_info(job_id) - self.assertTrue(job_info['status'], 'failed') - - @unittest.skip("We do not want to store login information for automated testing") - def test_delete_job(self): - """ Submit a job and then cancel/delete it, regardless of its status. Check job history before and after. """ - - ionq_api = IonQConnection() - - job_id = ionq_api.job_submit('simulator', json_circ1, 100, 'test_simulator_json_job') - job_history_df_before = ionq_api.job_get_history() - assert(job_id in job_history_df_before.id.values) - ionq_api.job_cancel(job_id) - job_history_df_after = ionq_api.job_get_history() - assert(job_id not in job_history_df_after.id.values) - - -if __name__ == "__main__": - unittest.main() diff --git a/qsdk/problem_decomposition/oniom/__init__.py b/qsdk/problem_decomposition/oniom/__init__.py deleted file mode 100644 index 2ef4bc754..000000000 --- a/qsdk/problem_decomposition/oniom/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright 2021 1QB Information Technologies Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - diff --git a/qsdk/problem_decomposition/oniom/_helpers/__init__.py b/qsdk/problem_decomposition/oniom/_helpers/__init__.py deleted file mode 100644 index 2ef4bc754..000000000 --- a/qsdk/problem_decomposition/oniom/_helpers/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright 2021 1QB Information Technologies Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - diff --git a/qsdk/problem_decomposition/tests/__init__.py b/qsdk/problem_decomposition/tests/__init__.py deleted file mode 100644 index 2ef4bc754..000000000 --- a/qsdk/problem_decomposition/tests/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright 2021 1QB Information Technologies Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - diff --git a/qsdk/problem_decomposition/tests/dmet/__init__.py b/qsdk/problem_decomposition/tests/dmet/__init__.py deleted file mode 100644 index 2ef4bc754..000000000 --- a/qsdk/problem_decomposition/tests/dmet/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright 2021 1QB Information Technologies Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - diff --git a/qsdk/problem_decomposition/tests/oniom/__init__.py b/qsdk/problem_decomposition/tests/oniom/__init__.py deleted file mode 100644 index 2ef4bc754..000000000 --- a/qsdk/problem_decomposition/tests/oniom/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright 2021 1QB Information Technologies Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - diff --git a/qsdk/toolboxes/__init__.py b/qsdk/toolboxes/__init__.py deleted file mode 100644 index 2ef4bc754..000000000 --- a/qsdk/toolboxes/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright 2021 1QB Information Technologies Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - diff --git a/qsdk/toolboxes/ansatz_generator/__init__.py b/qsdk/toolboxes/ansatz_generator/__init__.py deleted file mode 100644 index 2ef4bc754..000000000 --- a/qsdk/toolboxes/ansatz_generator/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright 2021 1QB Information Technologies Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - diff --git a/qsdk/toolboxes/ansatz_generator/tests/__init__.py b/qsdk/toolboxes/ansatz_generator/tests/__init__.py deleted file mode 100644 index 2ef4bc754..000000000 --- a/qsdk/toolboxes/ansatz_generator/tests/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright 2021 1QB Information Technologies Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - diff --git a/qsdk/toolboxes/measurements/tests/__init__.py b/qsdk/toolboxes/measurements/tests/__init__.py deleted file mode 100644 index 2ef4bc754..000000000 --- a/qsdk/toolboxes/measurements/tests/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright 2021 1QB Information Technologies Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - diff --git a/qsdk/toolboxes/molecular_computation/__init__.py b/qsdk/toolboxes/molecular_computation/__init__.py deleted file mode 100644 index 2ef4bc754..000000000 --- a/qsdk/toolboxes/molecular_computation/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright 2021 1QB Information Technologies Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - diff --git a/qsdk/toolboxes/molecular_computation/tests/__init__.py b/qsdk/toolboxes/molecular_computation/tests/__init__.py deleted file mode 100644 index 2ef4bc754..000000000 --- a/qsdk/toolboxes/molecular_computation/tests/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright 2021 1QB Information Technologies Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - diff --git a/qsdk/toolboxes/operators/tests/__init__.py b/qsdk/toolboxes/operators/tests/__init__.py deleted file mode 100644 index 2ef4bc754..000000000 --- a/qsdk/toolboxes/operators/tests/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright 2021 1QB Information Technologies Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - diff --git a/qsdk/toolboxes/post_processing/tests/__init__.py b/qsdk/toolboxes/post_processing/tests/__init__.py deleted file mode 100644 index 2ef4bc754..000000000 --- a/qsdk/toolboxes/post_processing/tests/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright 2021 1QB Information Technologies Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - diff --git a/qsdk/toolboxes/qubit_mappings/tests/__init__.py b/qsdk/toolboxes/qubit_mappings/tests/__init__.py deleted file mode 100644 index 2ef4bc754..000000000 --- a/qsdk/toolboxes/qubit_mappings/tests/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright 2021 1QB Information Technologies Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - diff --git a/setup.py b/setup.py index f1876a3b4..741f3d54d 100755 --- a/setup.py +++ b/setup.py @@ -10,25 +10,21 @@ def install(package): with open('README.rst', 'r') as f: long_description = f.read() -__title__ = "1QBit's quantum SDK for quantum chemistry" -__copyright__ = "1QBit Inc" -__version__ = "0.2.0" -__status__ = "beta" -__authors__ = ["Valentin Senicourt, Alexandre Fleury, Ryan Day, James Brown"] - install('wheel') install('h5py==3.2.0') install('pyscf==1.7.6') setuptools.setup( - name="qSDK", - version=__version__, - description="1QBit's quantum SDK for quantum chemistry on quantum computers and simulators", + name="tangelo", + author="The Tangelo developers", + version="0.2.0", + description="Open-source quantum SDK developed for exploring quantum chemistry simulation end-to-end workflows on " + "gate-model quantum computers", long_description=long_description, long_description_content_type="text/x-rst", - url="https://github.com/quantumsimulation/QEMIST_qSDK", + url="https://github.com/quantumsimulation/QEMIST_Tangelo", packages=setuptools.find_packages(), - test_suite="qsdk", + test_suite="tangelo", setup_requires=['h5py==3.2.0'], install_requires=['h5py==3.2.0', 'bitarray', 'openfermion', 'openfermionpyscf'] ) diff --git a/qsdk/__init__.py b/tangelo/__init__.py similarity index 85% rename from qsdk/__init__.py rename to tangelo/__init__.py index 3ceae4420..67c8a4a00 100644 --- a/qsdk/__init__.py +++ b/tangelo/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -21,4 +21,4 @@ warnings.filterwarnings("ignore", category=DeprecationWarning) sup.filter(np.core) -from qsdk.toolboxes.molecular_computation.molecule import Molecule, SecondQuantizedMolecule +from tangelo.toolboxes.molecular_computation.molecule import Molecule, SecondQuantizedMolecule diff --git a/qsdk/algorithms/__init__.py b/tangelo/algorithms/__init__.py similarity index 92% rename from qsdk/algorithms/__init__.py rename to tangelo/algorithms/__init__.py index d21d6dfd6..17c6ccafc 100644 --- a/qsdk/algorithms/__init__.py +++ b/tangelo/algorithms/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/algorithms/classical/__init__.py b/tangelo/algorithms/classical/__init__.py similarity index 92% rename from qsdk/algorithms/classical/__init__.py rename to tangelo/algorithms/classical/__init__.py index 15e003264..e37946d63 100644 --- a/qsdk/algorithms/classical/__init__.py +++ b/tangelo/algorithms/classical/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/algorithms/classical/ccsd_solver.py b/tangelo/algorithms/classical/ccsd_solver.py similarity index 95% rename from qsdk/algorithms/classical/ccsd_solver.py rename to tangelo/algorithms/classical/ccsd_solver.py index f80c286b8..90d621cc9 100644 --- a/qsdk/algorithms/classical/ccsd_solver.py +++ b/tangelo/algorithms/classical/ccsd_solver.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ from pyscf import cc, lib from pyscf.cc.ccsd_rdm import _make_rdm1, _make_rdm2, _gamma1_intermediates, _gamma2_outcore -from qsdk.algorithms.electronic_structure_solver import ElectronicStructureSolver +from tangelo.algorithms.electronic_structure_solver import ElectronicStructureSolver class CCSDSolver(ElectronicStructureSolver): diff --git a/qsdk/algorithms/classical/fci_solver.py b/tangelo/algorithms/classical/fci_solver.py similarity index 97% rename from qsdk/algorithms/classical/fci_solver.py rename to tangelo/algorithms/classical/fci_solver.py index e70803e53..68b37de12 100644 --- a/qsdk/algorithms/classical/fci_solver.py +++ b/tangelo/algorithms/classical/fci_solver.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ from pyscf import ao2mo, fci, mcscf -from qsdk.algorithms.electronic_structure_solver import ElectronicStructureSolver +from tangelo.algorithms.electronic_structure_solver import ElectronicStructureSolver class FCISolver(ElectronicStructureSolver): diff --git a/qsdk/algorithms/classical/semi_empirical_solver.py b/tangelo/algorithms/classical/semi_empirical_solver.py similarity index 95% rename from qsdk/algorithms/classical/semi_empirical_solver.py rename to tangelo/algorithms/classical/semi_empirical_solver.py index ce7c1986b..cb6aaa0c8 100644 --- a/qsdk/algorithms/classical/semi_empirical_solver.py +++ b/tangelo/algorithms/classical/semi_empirical_solver.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -32,7 +32,7 @@ from pyscf.semiempirical import mindo3 -from qsdk.algorithms.electronic_structure_solver import ElectronicStructureSolver +from tangelo.algorithms.electronic_structure_solver import ElectronicStructureSolver class MINDO3Solver(ElectronicStructureSolver): diff --git a/qsdk/problem_decomposition/dmet/__init__.py b/tangelo/algorithms/classical/tests/__init__.py similarity index 91% rename from qsdk/problem_decomposition/dmet/__init__.py rename to tangelo/algorithms/classical/tests/__init__.py index 2ef4bc754..81a799660 100644 --- a/qsdk/problem_decomposition/dmet/__init__.py +++ b/tangelo/algorithms/classical/tests/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/algorithms/classical/tests/test_ccsd_solver.py b/tangelo/algorithms/classical/tests/test_ccsd_solver.py similarity index 93% rename from qsdk/algorithms/classical/tests/test_ccsd_solver.py rename to tangelo/algorithms/classical/tests/test_ccsd_solver.py index 4552ac177..52df2b494 100644 --- a/qsdk/algorithms/classical/tests/test_ccsd_solver.py +++ b/tangelo/algorithms/classical/tests/test_ccsd_solver.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,8 +14,8 @@ import unittest -from qsdk.algorithms.classical.ccsd_solver import CCSDSolver -from qsdk.molecule_library import mol_H2_321g, mol_Be_321g +from tangelo.algorithms.classical.ccsd_solver import CCSDSolver +from tangelo.molecule_library import mol_H2_321g, mol_Be_321g # TODO: Can we test the get_rdm method on H2 ? How do we get our reference? Whole matrix or its properties? diff --git a/qsdk/algorithms/classical/tests/test_fci_solver.py b/tangelo/algorithms/classical/tests/test_fci_solver.py similarity index 93% rename from qsdk/algorithms/classical/tests/test_fci_solver.py rename to tangelo/algorithms/classical/tests/test_fci_solver.py index 7bff1b836..3bffc9c62 100644 --- a/qsdk/algorithms/classical/tests/test_fci_solver.py +++ b/tangelo/algorithms/classical/tests/test_fci_solver.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,8 +14,8 @@ import unittest -from qsdk.algorithms import FCISolver -from qsdk.molecule_library import mol_H2_321g, mol_Be_321g, mol_H4_cation_sto3g +from tangelo.algorithms import FCISolver +from tangelo.molecule_library import mol_H2_321g, mol_Be_321g, mol_H4_cation_sto3g # TODO: Can we test the get_rdm method on H2 ? How do we get our reference? Whole matrix or its properties? diff --git a/qsdk/algorithms/classical/tests/test_semi_empirical_solver.py b/tangelo/algorithms/classical/tests/test_semi_empirical_solver.py similarity index 87% rename from qsdk/algorithms/classical/tests/test_semi_empirical_solver.py rename to tangelo/algorithms/classical/tests/test_semi_empirical_solver.py index 281485d9f..afe33f951 100644 --- a/qsdk/algorithms/classical/tests/test_semi_empirical_solver.py +++ b/tangelo/algorithms/classical/tests/test_semi_empirical_solver.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,8 +14,8 @@ import unittest -from qsdk.molecule_library import mol_pyridine -from qsdk.algorithms.classical.semi_empirical_solver import MINDO3Solver +from tangelo.molecule_library import mol_pyridine +from tangelo.algorithms.classical.semi_empirical_solver import MINDO3Solver class MINDO3SolverTest(unittest.TestCase): diff --git a/qsdk/algorithms/electronic_structure_solver.py b/tangelo/algorithms/electronic_structure_solver.py similarity index 97% rename from qsdk/algorithms/electronic_structure_solver.py rename to tangelo/algorithms/electronic_structure_solver.py index 63ee6c916..f54f1c688 100644 --- a/qsdk/algorithms/electronic_structure_solver.py +++ b/tangelo/algorithms/electronic_structure_solver.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/algorithms/variational/__init__.py b/tangelo/algorithms/variational/__init__.py similarity index 92% rename from qsdk/algorithms/variational/__init__.py rename to tangelo/algorithms/variational/__init__.py index 4b330ce0a..4e5e3fbca 100644 --- a/qsdk/algorithms/variational/__init__.py +++ b/tangelo/algorithms/variational/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/algorithms/variational/adapt_vqe_solver.py b/tangelo/algorithms/variational/adapt_vqe_solver.py similarity index 95% rename from qsdk/algorithms/variational/adapt_vqe_solver.py rename to tangelo/algorithms/variational/adapt_vqe_solver.py index 174fbf127..9910435f6 100644 --- a/qsdk/algorithms/variational/adapt_vqe_solver.py +++ b/tangelo/algorithms/variational/adapt_vqe_solver.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -29,15 +29,15 @@ import math from openfermion import commutator from openfermion import FermionOperator as ofFermionOperator -from qsdk.toolboxes.operators.operators import FermionOperator, QubitOperator +from tangelo.toolboxes.operators.operators import FermionOperator, QubitOperator from scipy.optimize import minimize import warnings -from qsdk.toolboxes.ansatz_generator.adapt_ansatz import ADAPTAnsatz -from qsdk.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping -from qsdk.toolboxes.ansatz_generator._general_unitary_cc import uccgsd_generator as uccgsd_pool -from qsdk.toolboxes.operators import qubitop_to_qubitham -from qsdk.algorithms.variational.vqe_solver import VQESolver +from tangelo.toolboxes.ansatz_generator.adapt_ansatz import ADAPTAnsatz +from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping +from tangelo.toolboxes.ansatz_generator._general_unitary_cc import uccgsd_generator as uccgsd_pool +from tangelo.toolboxes.operators import qubitop_to_qubitham +from tangelo.algorithms.variational.vqe_solver import VQESolver class ADAPTSolver: @@ -249,7 +249,7 @@ def rank_pool(self, pool_commutators, circuit, backend, tolerance=1e-3): generator. circuit (agnostic_simulator.Circuit): Circuit for measuring each commutator. - backend (qsdk.backendbuddy.Simulator): Backend to measure + backend (tangelo.backendbuddy.Simulator): Backend to measure expectation values. tolerance (float): Minimum value for gradient to be considered. diff --git a/qsdk/algorithms/classical/tests/__init__.py b/tangelo/algorithms/variational/tests/__init__.py similarity index 91% rename from qsdk/algorithms/classical/tests/__init__.py rename to tangelo/algorithms/variational/tests/__init__.py index 2ef4bc754..81a799660 100644 --- a/qsdk/algorithms/classical/tests/__init__.py +++ b/tangelo/algorithms/variational/tests/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/algorithms/variational/tests/test_adapt_vqe_solver.py b/tangelo/algorithms/variational/tests/test_adapt_vqe_solver.py similarity index 93% rename from qsdk/algorithms/variational/tests/test_adapt_vqe_solver.py rename to tangelo/algorithms/variational/tests/test_adapt_vqe_solver.py index 1841bd962..146b76e69 100644 --- a/qsdk/algorithms/variational/tests/test_adapt_vqe_solver.py +++ b/tangelo/algorithms/variational/tests/test_adapt_vqe_solver.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,8 +14,8 @@ import unittest -from qsdk.algorithms.variational import ADAPTSolver -from qsdk.molecule_library import mol_H2_sto3g +from tangelo.algorithms.variational import ADAPTSolver +from tangelo.molecule_library import mol_H2_sto3g class ADAPTSolverTest(unittest.TestCase): diff --git a/qsdk/algorithms/variational/tests/test_vqe_solver.py b/tangelo/algorithms/variational/tests/test_vqe_solver.py similarity index 97% rename from qsdk/algorithms/variational/tests/test_vqe_solver.py rename to tangelo/algorithms/variational/tests/test_vqe_solver.py index 2d597d400..c9aadf561 100644 --- a/qsdk/algorithms/variational/tests/test_vqe_solver.py +++ b/tangelo/algorithms/variational/tests/test_vqe_solver.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,12 +15,12 @@ import unittest import numpy as np -from qsdk.backendbuddy import Simulator -from qsdk.algorithms import BuiltInAnsatze, VQESolver -from qsdk.molecule_library import mol_H2_sto3g, mol_H4_sto3g, mol_H4_cation_sto3g, mol_NaH_sto3g, mol_NaH_sto3g -from qsdk.toolboxes.ansatz_generator.uccsd import UCCSD -from qsdk.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping -from qsdk.toolboxes.molecular_computation.rdms import matricize_2rdm +from tangelo.backendbuddy import Simulator +from tangelo.algorithms import BuiltInAnsatze, VQESolver +from tangelo.molecule_library import mol_H2_sto3g, mol_H4_sto3g, mol_H4_cation_sto3g, mol_NaH_sto3g +from tangelo.toolboxes.ansatz_generator.uccsd import UCCSD +from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping +from tangelo.toolboxes.molecular_computation.rdms import matricize_2rdm class VQESolverTest(unittest.TestCase): diff --git a/qsdk/algorithms/variational/vqe_solver.py b/tangelo/algorithms/variational/vqe_solver.py similarity index 95% rename from qsdk/algorithms/variational/vqe_solver.py rename to tangelo/algorithms/variational/vqe_solver.py index a36c0bfba..49e55d978 100644 --- a/qsdk/algorithms/variational/vqe_solver.py +++ b/tangelo/algorithms/variational/vqe_solver.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,20 +23,20 @@ import numpy as np from openfermion.ops.operators.qubit_operator import QubitOperator -from qsdk.helpers.utils import HiddenPrints -from qsdk.backendbuddy import Simulator, Circuit -from qsdk.backendbuddy.helpers.circuits.measurement_basis import measurement_basis_gates -from qsdk.toolboxes.operators import count_qubits, FermionOperator, qubitop_to_qubitham -from qsdk.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping -from qsdk.toolboxes.ansatz_generator.ansatz import Ansatz -from qsdk.toolboxes.ansatz_generator.uccsd import UCCSD -from qsdk.toolboxes.ansatz_generator.rucc import RUCC -from qsdk.toolboxes.ansatz_generator.hea import HEA -from qsdk.toolboxes.ansatz_generator.upccgsd import UpCCGSD -from qsdk.toolboxes.ansatz_generator.variational_circuit import VariationalCircuitAnsatz -from qsdk.toolboxes.ansatz_generator.penalty_terms import combined_penalty -from qsdk.toolboxes.post_processing.bootstrapping import get_resampled_frequencies -from qsdk.toolboxes.ansatz_generator.fermionic_operators import number_operator, spinz_operator, spin2_operator +from tangelo.helpers.utils import HiddenPrints +from tangelo.backendbuddy import Simulator, Circuit +from tangelo.backendbuddy.helpers.circuits.measurement_basis import measurement_basis_gates +from tangelo.toolboxes.operators import count_qubits, FermionOperator, qubitop_to_qubitham +from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping +from tangelo.toolboxes.ansatz_generator.ansatz import Ansatz +from tangelo.toolboxes.ansatz_generator.uccsd import UCCSD +from tangelo.toolboxes.ansatz_generator.rucc import RUCC +from tangelo.toolboxes.ansatz_generator.hea import HEA +from tangelo.toolboxes.ansatz_generator.upccgsd import UpCCGSD +from tangelo.toolboxes.ansatz_generator.variational_circuit import VariationalCircuitAnsatz +from tangelo.toolboxes.ansatz_generator.penalty_terms import combined_penalty +from tangelo.toolboxes.post_processing.bootstrapping import get_resampled_frequencies +from tangelo.toolboxes.ansatz_generator.fermionic_operators import number_operator, spinz_operator, spin2_operator class BuiltInAnsatze(Enum): @@ -70,7 +70,7 @@ class VQESolver: and its behavior. initial_var_params (str or array-like) : initial value for the classical optimizer. - backend_options (dict) : parameters to build the qsdk.backendbuddy Simulator + backend_options (dict) : parameters to build the tangelo.backendbuddy Simulator class. penalty_terms (dict): parameters for penalty terms to append to target qubit Hamiltonian (see penaly_terms for more details). diff --git a/qsdk/backendbuddy/__init__.py b/tangelo/backendbuddy/__init__.py similarity index 92% rename from qsdk/backendbuddy/__init__.py rename to tangelo/backendbuddy/__init__.py index 76a1bf0ad..582bf8ed3 100644 --- a/qsdk/backendbuddy/__init__.py +++ b/tangelo/backendbuddy/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/backendbuddy/circuit.py b/tangelo/backendbuddy/circuit.py similarity index 98% rename from qsdk/backendbuddy/circuit.py rename to tangelo/backendbuddy/circuit.py index d3efc88c2..ffcf680dc 100644 --- a/qsdk/backendbuddy/circuit.py +++ b/tangelo/backendbuddy/circuit.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ """ from typing import List -from qsdk.backendbuddy import Gate +from tangelo.backendbuddy import Gate class Circuit: diff --git a/qsdk/backendbuddy/gate.py b/tangelo/backendbuddy/gate.py similarity index 98% rename from qsdk/backendbuddy/gate.py rename to tangelo/backendbuddy/gate.py index 64a4ad638..df6ce1c65 100644 --- a/qsdk/backendbuddy/gate.py +++ b/tangelo/backendbuddy/gate.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/backendbuddy/helpers/__init__.py b/tangelo/backendbuddy/helpers/__init__.py similarity index 92% rename from qsdk/backendbuddy/helpers/__init__.py rename to tangelo/backendbuddy/helpers/__init__.py index bfce502cd..43e6dcc12 100644 --- a/qsdk/backendbuddy/helpers/__init__.py +++ b/tangelo/backendbuddy/helpers/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/backendbuddy/helpers/circuits/__init__.py b/tangelo/backendbuddy/helpers/circuits/__init__.py similarity index 91% rename from qsdk/backendbuddy/helpers/circuits/__init__.py rename to tangelo/backendbuddy/helpers/circuits/__init__.py index 86b2719a7..a09181e3d 100644 --- a/qsdk/backendbuddy/helpers/circuits/__init__.py +++ b/tangelo/backendbuddy/helpers/circuits/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/backendbuddy/helpers/circuits/measurement_basis.py b/tangelo/backendbuddy/helpers/circuits/measurement_basis.py similarity index 95% rename from qsdk/backendbuddy/helpers/circuits/measurement_basis.py rename to tangelo/backendbuddy/helpers/circuits/measurement_basis.py index de4fe08b8..693623165 100644 --- a/qsdk/backendbuddy/helpers/circuits/measurement_basis.py +++ b/tangelo/backendbuddy/helpers/circuits/measurement_basis.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ """Helper function: pauli word rotations.""" import numpy as np -from qsdk.backendbuddy import Gate +from tangelo.backendbuddy import Gate def measurement_basis_gates(term): diff --git a/qsdk/backendbuddy/helpers/operators/__init__.py b/tangelo/backendbuddy/helpers/operators/__init__.py similarity index 91% rename from qsdk/backendbuddy/helpers/operators/__init__.py rename to tangelo/backendbuddy/helpers/operators/__init__.py index e157411d2..b2caf5b03 100644 --- a/qsdk/backendbuddy/helpers/operators/__init__.py +++ b/tangelo/backendbuddy/helpers/operators/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/backendbuddy/helpers/operators/operators.py b/tangelo/backendbuddy/helpers/operators/operators.py similarity index 97% rename from qsdk/backendbuddy/helpers/operators/operators.py rename to tangelo/backendbuddy/helpers/operators/operators.py index 0e3c276fb..a4815109c 100644 --- a/qsdk/backendbuddy/helpers/operators/operators.py +++ b/tangelo/backendbuddy/helpers/operators/operators.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/backendbuddy/noisy_simulation/__init__.py b/tangelo/backendbuddy/noisy_simulation/__init__.py similarity index 91% rename from qsdk/backendbuddy/noisy_simulation/__init__.py rename to tangelo/backendbuddy/noisy_simulation/__init__.py index dfa75f6d5..452521067 100644 --- a/qsdk/backendbuddy/noisy_simulation/__init__.py +++ b/tangelo/backendbuddy/noisy_simulation/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/backendbuddy/noisy_simulation/noise_models.py b/tangelo/backendbuddy/noisy_simulation/noise_models.py similarity index 98% rename from qsdk/backendbuddy/noisy_simulation/noise_models.py rename to tangelo/backendbuddy/noisy_simulation/noise_models.py index 441a698a9..80ea95d28 100644 --- a/qsdk/backendbuddy/noisy_simulation/noise_models.py +++ b/tangelo/backendbuddy/noisy_simulation/noise_models.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ """ -from qsdk.backendbuddy import ONE_QUBIT_GATES +from tangelo.backendbuddy import ONE_QUBIT_GATES SUPPORTED_NOISE_MODELS = {'depol', 'pauli'} diff --git a/qsdk/backendbuddy/qdk_template.py b/tangelo/backendbuddy/qdk_template.py similarity index 97% rename from qsdk/backendbuddy/qdk_template.py rename to tangelo/backendbuddy/qdk_template.py index 0b935e349..6dc185f6b 100644 --- a/qsdk/backendbuddy/qdk_template.py +++ b/tangelo/backendbuddy/qdk_template.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/backendbuddy/qpu_connection/__init__.py b/tangelo/backendbuddy/qpu_connection/__init__.py similarity index 81% rename from qsdk/backendbuddy/qpu_connection/__init__.py rename to tangelo/backendbuddy/qpu_connection/__init__.py index b4e74ae1e..22e5b5e95 100644 --- a/qsdk/backendbuddy/qpu_connection/__init__.py +++ b/tangelo/backendbuddy/qpu_connection/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,6 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -from .honeywell_connection import HoneywellConnection -from .ionq_connection import IonQConnection from .qemist_cloud_connection import job_submit, job_status, job_cancel, job_result, job_estimate diff --git a/qsdk/backendbuddy/qpu_connection/qemist_cloud_connection.py b/tangelo/backendbuddy/qpu_connection/qemist_cloud_connection.py similarity index 97% rename from qsdk/backendbuddy/qpu_connection/qemist_cloud_connection.py rename to tangelo/backendbuddy/qpu_connection/qemist_cloud_connection.py index 88738cc6f..f4ab4edd5 100644 --- a/qsdk/backendbuddy/qpu_connection/qemist_cloud_connection.py +++ b/tangelo/backendbuddy/qpu_connection/qemist_cloud_connection.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -77,7 +77,7 @@ def job_cancel(qemist_cloud_job_id): """ res = util.cancel_problems(qemist_cloud_job_id) - # TODO: If res is coming out as an error code, qSDK should raise an error + # TODO: If res is coming out as an error code, Tangelo should raise an error return res diff --git a/qsdk/backendbuddy/simulator.py b/tangelo/backendbuddy/simulator.py similarity index 98% rename from qsdk/backendbuddy/simulator.py rename to tangelo/backendbuddy/simulator.py index a0c50af4f..f59289bc6 100644 --- a/qsdk/backendbuddy/simulator.py +++ b/tangelo/backendbuddy/simulator.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -38,10 +38,10 @@ from bitarray import bitarray from openfermion.ops import QubitOperator -from qsdk.helpers.utils import default_simulator -from qsdk.backendbuddy import Gate, Circuit -from qsdk.backendbuddy.helpers.circuits.measurement_basis import measurement_basis_gates -import qsdk.backendbuddy.translator as translator +from tangelo.helpers.utils import default_simulator +from tangelo.backendbuddy import Gate, Circuit +from tangelo.backendbuddy.helpers.circuits.measurement_basis import measurement_basis_gates +import tangelo.backendbuddy.translator as translator # Data-structure showing what functionalities are supported by the backend, in this package @@ -184,7 +184,7 @@ def simulate(self, source_circuit, return_statevector=False, initial_statevector # Drawing individual shots with the qasm simulator, for noisy simulation or simulating mixed states if self._noise_model or source_circuit.is_mixed_state: - from qsdk.backendbuddy.noisy_simulation.noise_models import get_qiskit_noise_model + from tangelo.backendbuddy.noisy_simulation.noise_models import get_qiskit_noise_model meas_range = range(source_circuit.width) translated_circuit.measure(meas_range, meas_range) diff --git a/qsdk/backendbuddy/tests/__init__.py b/tangelo/backendbuddy/tests/__init__.py similarity index 91% rename from qsdk/backendbuddy/tests/__init__.py rename to tangelo/backendbuddy/tests/__init__.py index 2ef4bc754..81a799660 100644 --- a/qsdk/backendbuddy/tests/__init__.py +++ b/tangelo/backendbuddy/tests/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/backendbuddy/tests/data/H2_UCCSD.qasm b/tangelo/backendbuddy/tests/data/H2_UCCSD.qasm similarity index 100% rename from qsdk/backendbuddy/tests/data/H2_UCCSD.qasm rename to tangelo/backendbuddy/tests/data/H2_UCCSD.qasm diff --git a/qsdk/backendbuddy/tests/data/H2_qubit_hamiltonian.txt b/tangelo/backendbuddy/tests/data/H2_qubit_hamiltonian.txt similarity index 100% rename from qsdk/backendbuddy/tests/data/H2_qubit_hamiltonian.txt rename to tangelo/backendbuddy/tests/data/H2_qubit_hamiltonian.txt diff --git a/qsdk/backendbuddy/tests/data/H4_UCCSD.qasm b/tangelo/backendbuddy/tests/data/H4_UCCSD.qasm similarity index 100% rename from qsdk/backendbuddy/tests/data/H4_UCCSD.qasm rename to tangelo/backendbuddy/tests/data/H4_UCCSD.qasm diff --git a/qsdk/backendbuddy/tests/data/H4_qubit_hamiltonian.txt b/tangelo/backendbuddy/tests/data/H4_qubit_hamiltonian.txt similarity index 100% rename from qsdk/backendbuddy/tests/data/H4_qubit_hamiltonian.txt rename to tangelo/backendbuddy/tests/data/H4_qubit_hamiltonian.txt diff --git a/qsdk/backendbuddy/tests/data/projectq_circuit.txt b/tangelo/backendbuddy/tests/data/projectq_circuit.txt similarity index 100% rename from qsdk/backendbuddy/tests/data/projectq_circuit.txt rename to tangelo/backendbuddy/tests/data/projectq_circuit.txt diff --git a/qsdk/backendbuddy/tests/test_circuits.py b/tangelo/backendbuddy/tests/test_circuits.py similarity index 97% rename from qsdk/backendbuddy/tests/test_circuits.py rename to tangelo/backendbuddy/tests/test_circuits.py index e4a5a98e8..9feb8d9e1 100644 --- a/qsdk/backendbuddy/tests/test_circuits.py +++ b/tangelo/backendbuddy/tests/test_circuits.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import unittest import copy from collections import Counter -from qsdk.backendbuddy import Gate, Circuit +from tangelo.backendbuddy import Gate, Circuit # Create several abstract circuits with different features mygates = list() diff --git a/qsdk/backendbuddy/tests/test_gates.py b/tangelo/backendbuddy/tests/test_gates.py similarity index 95% rename from qsdk/backendbuddy/tests/test_gates.py rename to tangelo/backendbuddy/tests/test_gates.py index 9871820eb..78a4680b8 100644 --- a/qsdk/backendbuddy/tests/test_gates.py +++ b/tangelo/backendbuddy/tests/test_gates.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ # limitations under the License. import unittest -from qsdk.backendbuddy import Gate +from tangelo.backendbuddy import Gate class TestGates(unittest.TestCase): diff --git a/qsdk/backendbuddy/tests/test_simulator.py b/tangelo/backendbuddy/tests/test_simulator.py similarity index 98% rename from qsdk/backendbuddy/tests/test_simulator.py rename to tangelo/backendbuddy/tests/test_simulator.py index 49e691214..b6dff1713 100644 --- a/qsdk/backendbuddy/tests/test_simulator.py +++ b/tangelo/backendbuddy/tests/test_simulator.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,9 +23,9 @@ import numpy as np from openfermion.ops import QubitOperator -from qsdk.backendbuddy import Gate, Circuit, translator, Simulator -from qsdk.backendbuddy.helpers import string_ham_to_of -from qsdk.helpers.utils import installed_simulator, installed_sv_simulator, installed_backends +from tangelo.backendbuddy import Gate, Circuit, translator, Simulator +from tangelo.backendbuddy.helpers import string_ham_to_of +from tangelo.helpers.utils import installed_simulator, installed_sv_simulator, installed_backends path_data = os.path.dirname(os.path.abspath(__file__)) + '/data' diff --git a/qsdk/backendbuddy/tests/test_simulator_noisy.py b/tangelo/backendbuddy/tests/test_simulator_noisy.py similarity index 97% rename from qsdk/backendbuddy/tests/test_simulator_noisy.py rename to tangelo/backendbuddy/tests/test_simulator_noisy.py index 9cf108869..f8ac19066 100644 --- a/qsdk/backendbuddy/tests/test_simulator_noisy.py +++ b/tangelo/backendbuddy/tests/test_simulator_noisy.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,9 +20,9 @@ from openfermion.ops import QubitOperator -from qsdk.backendbuddy import Gate, Circuit, Simulator, backend_info -from qsdk.backendbuddy.noisy_simulation import NoiseModel, get_qiskit_noise_dict -from qsdk.helpers.utils import default_simulator, installed_backends +from tangelo.backendbuddy import Gate, Circuit, Simulator, backend_info +from tangelo.backendbuddy.noisy_simulation import NoiseModel, get_qiskit_noise_dict +from tangelo.helpers.utils import default_simulator, installed_backends # Noisy simulation: circuits, noise models, references cn1 = Circuit([Gate('X', target=0)]) diff --git a/qsdk/backendbuddy/tests/test_translator.py b/tangelo/backendbuddy/tests/test_translator.py similarity index 98% rename from qsdk/backendbuddy/tests/test_translator.py rename to tangelo/backendbuddy/tests/test_translator.py index fd34e02f7..0a0bc5766 100644 --- a/qsdk/backendbuddy/tests/test_translator.py +++ b/tangelo/backendbuddy/tests/test_translator.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -21,9 +21,9 @@ import os import numpy as np -from qsdk.backendbuddy import Gate, Circuit -import qsdk.backendbuddy.translator as translator -from qsdk.helpers.utils import installed_backends +from tangelo.backendbuddy import Gate, Circuit +import tangelo.backendbuddy.translator as translator +from tangelo.helpers.utils import installed_backends path_data = os.path.dirname(os.path.realpath(__file__)) + '/data' diff --git a/qsdk/backendbuddy/translator/__init__.py b/tangelo/backendbuddy/translator/__init__.py similarity index 93% rename from qsdk/backendbuddy/translator/__init__.py rename to tangelo/backendbuddy/translator/__init__.py index c4125cf97..02da3c729 100644 --- a/qsdk/backendbuddy/translator/__init__.py +++ b/tangelo/backendbuddy/translator/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from qsdk.helpers.utils import installed_backends +from tangelo.helpers.utils import installed_backends from .translate_braket import translate_braket, get_braket_gates from .translate_qiskit import translate_qiskit, get_qiskit_gates diff --git a/qsdk/backendbuddy/translator/qdk_template.py b/tangelo/backendbuddy/translator/qdk_template.py similarity index 97% rename from qsdk/backendbuddy/translator/qdk_template.py rename to tangelo/backendbuddy/translator/qdk_template.py index 478b420f8..c582e7605 100644 --- a/qsdk/backendbuddy/translator/qdk_template.py +++ b/tangelo/backendbuddy/translator/qdk_template.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/backendbuddy/translator/translate_braket.py b/tangelo/backendbuddy/translator/translate_braket.py similarity index 98% rename from qsdk/backendbuddy/translator/translate_braket.py rename to tangelo/backendbuddy/translator/translate_braket.py index c186b0f93..a88a7d6df 100644 --- a/qsdk/backendbuddy/translator/translate_braket.py +++ b/tangelo/backendbuddy/translator/translate_braket.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/backendbuddy/translator/translate_cirq.py b/tangelo/backendbuddy/translator/translate_cirq.py similarity index 98% rename from qsdk/backendbuddy/translator/translate_cirq.py rename to tangelo/backendbuddy/translator/translate_cirq.py index fd5f20a88..e9c4f27fe 100644 --- a/qsdk/backendbuddy/translator/translate_cirq.py +++ b/tangelo/backendbuddy/translator/translate_cirq.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/backendbuddy/translator/translate_json_ionq.py b/tangelo/backendbuddy/translator/translate_json_ionq.py similarity index 97% rename from qsdk/backendbuddy/translator/translate_json_ionq.py rename to tangelo/backendbuddy/translator/translate_json_ionq.py index b35162dea..e387d5e7e 100644 --- a/qsdk/backendbuddy/translator/translate_json_ionq.py +++ b/tangelo/backendbuddy/translator/translate_json_ionq.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/backendbuddy/translator/translate_openqasm.py b/tangelo/backendbuddy/translator/translate_openqasm.py similarity index 98% rename from qsdk/backendbuddy/translator/translate_openqasm.py rename to tangelo/backendbuddy/translator/translate_openqasm.py index b29af009d..3d3afba91 100644 --- a/qsdk/backendbuddy/translator/translate_openqasm.py +++ b/tangelo/backendbuddy/translator/translate_openqasm.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ import re from math import pi -from qsdk.backendbuddy import Gate, Circuit +from tangelo.backendbuddy import Gate, Circuit def get_openqasm_gates(): diff --git a/qsdk/backendbuddy/translator/translate_projectq.py b/tangelo/backendbuddy/translator/translate_projectq.py similarity index 98% rename from qsdk/backendbuddy/translator/translate_projectq.py rename to tangelo/backendbuddy/translator/translate_projectq.py index e1de3d08e..cd714061a 100644 --- a/qsdk/backendbuddy/translator/translate_projectq.py +++ b/tangelo/backendbuddy/translator/translate_projectq.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ """ import re -from qsdk.backendbuddy import Gate, Circuit +from tangelo.backendbuddy import Gate, Circuit def get_projectq_gates(): diff --git a/qsdk/backendbuddy/translator/translate_qdk.py b/tangelo/backendbuddy/translator/translate_qdk.py similarity index 98% rename from qsdk/backendbuddy/translator/translate_qdk.py rename to tangelo/backendbuddy/translator/translate_qdk.py index 5846624bf..541e41691 100644 --- a/qsdk/backendbuddy/translator/translate_qdk.py +++ b/tangelo/backendbuddy/translator/translate_qdk.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/backendbuddy/translator/translate_qiskit.py b/tangelo/backendbuddy/translator/translate_qiskit.py similarity index 98% rename from qsdk/backendbuddy/translator/translate_qiskit.py rename to tangelo/backendbuddy/translator/translate_qiskit.py index efb304ef1..597701193 100644 --- a/qsdk/backendbuddy/translator/translate_qiskit.py +++ b/tangelo/backendbuddy/translator/translate_qiskit.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/backendbuddy/translator/translate_qulacs.py b/tangelo/backendbuddy/translator/translate_qulacs.py similarity index 98% rename from qsdk/backendbuddy/translator/translate_qulacs.py rename to tangelo/backendbuddy/translator/translate_qulacs.py index c0982ef09..fcb288d21 100644 --- a/qsdk/backendbuddy/translator/translate_qulacs.py +++ b/tangelo/backendbuddy/translator/translate_qulacs.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/helpers/__init__.py b/tangelo/helpers/__init__.py similarity index 86% rename from qsdk/helpers/__init__.py rename to tangelo/helpers/__init__.py index 542733200..72b88bf48 100644 --- a/qsdk/helpers/__init__.py +++ b/tangelo/helpers/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,4 +13,4 @@ # limitations under the License. from .utils import * -from qsdk.backendbuddy.helpers import * +from tangelo.backendbuddy.helpers import * diff --git a/qsdk/helpers/utils.py b/tangelo/helpers/utils.py similarity index 97% rename from qsdk/helpers/utils.py rename to tangelo/helpers/utils.py index 789b65046..a04e7cc27 100644 --- a/qsdk/helpers/utils.py +++ b/tangelo/helpers/utils.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/molecule_library.py b/tangelo/molecule_library.py similarity index 97% rename from qsdk/molecule_library.py rename to tangelo/molecule_library.py index f15ee4759..31e32287a 100644 --- a/qsdk/molecule_library.py +++ b/tangelo/molecule_library.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ """Module to create some molecules used in the package unittest.""" -from qsdk import Molecule, SecondQuantizedMolecule +from tangelo import Molecule, SecondQuantizedMolecule # Dihydrogen. diff --git a/qsdk/problem_decomposition/__init__.py b/tangelo/problem_decomposition/__init__.py similarity index 92% rename from qsdk/problem_decomposition/__init__.py rename to tangelo/problem_decomposition/__init__.py index d28939a72..2ea539b70 100644 --- a/qsdk/problem_decomposition/__init__.py +++ b/tangelo/problem_decomposition/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/algorithms/variational/tests/__init__.py b/tangelo/problem_decomposition/dmet/__init__.py similarity index 91% rename from qsdk/algorithms/variational/tests/__init__.py rename to tangelo/problem_decomposition/dmet/__init__.py index 2ef4bc754..81a799660 100644 --- a/qsdk/algorithms/variational/tests/__init__.py +++ b/tangelo/problem_decomposition/dmet/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/problem_decomposition/dmet/_helpers/__init__.py b/tangelo/problem_decomposition/dmet/_helpers/__init__.py similarity index 95% rename from qsdk/problem_decomposition/dmet/_helpers/__init__.py rename to tangelo/problem_decomposition/dmet/_helpers/__init__.py index 625aea85a..c0e97f589 100644 --- a/qsdk/problem_decomposition/dmet/_helpers/__init__.py +++ b/tangelo/problem_decomposition/dmet/_helpers/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/problem_decomposition/dmet/_helpers/dmet_bath.py b/tangelo/problem_decomposition/dmet/_helpers/dmet_bath.py similarity index 99% rename from qsdk/problem_decomposition/dmet/_helpers/dmet_bath.py rename to tangelo/problem_decomposition/dmet/_helpers/dmet_bath.py index b0069be81..5311385cc 100644 --- a/qsdk/problem_decomposition/dmet/_helpers/dmet_bath.py +++ b/tangelo/problem_decomposition/dmet/_helpers/dmet_bath.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/problem_decomposition/dmet/_helpers/dmet_fragment.py b/tangelo/problem_decomposition/dmet/_helpers/dmet_fragment.py similarity index 98% rename from qsdk/problem_decomposition/dmet/_helpers/dmet_fragment.py rename to tangelo/problem_decomposition/dmet/_helpers/dmet_fragment.py index 5d5daf687..fc9ed6f29 100644 --- a/qsdk/problem_decomposition/dmet/_helpers/dmet_fragment.py +++ b/tangelo/problem_decomposition/dmet/_helpers/dmet_fragment.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/problem_decomposition/dmet/_helpers/dmet_onerdm.py b/tangelo/problem_decomposition/dmet/_helpers/dmet_onerdm.py similarity index 98% rename from qsdk/problem_decomposition/dmet/_helpers/dmet_onerdm.py rename to tangelo/problem_decomposition/dmet/_helpers/dmet_onerdm.py index 53eb59d04..35c4877ca 100644 --- a/qsdk/problem_decomposition/dmet/_helpers/dmet_onerdm.py +++ b/tangelo/problem_decomposition/dmet/_helpers/dmet_onerdm.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/problem_decomposition/dmet/_helpers/dmet_orbitals.py b/tangelo/problem_decomposition/dmet/_helpers/dmet_orbitals.py similarity index 99% rename from qsdk/problem_decomposition/dmet/_helpers/dmet_orbitals.py rename to tangelo/problem_decomposition/dmet/_helpers/dmet_orbitals.py index 42bc282d9..a63d74d2c 100644 --- a/qsdk/problem_decomposition/dmet/_helpers/dmet_orbitals.py +++ b/tangelo/problem_decomposition/dmet/_helpers/dmet_orbitals.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/problem_decomposition/dmet/_helpers/dmet_scf.py b/tangelo/problem_decomposition/dmet/_helpers/dmet_scf.py similarity index 98% rename from qsdk/problem_decomposition/dmet/_helpers/dmet_scf.py rename to tangelo/problem_decomposition/dmet/_helpers/dmet_scf.py index 438d695b5..db5194158 100644 --- a/qsdk/problem_decomposition/dmet/_helpers/dmet_scf.py +++ b/tangelo/problem_decomposition/dmet/_helpers/dmet_scf.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/problem_decomposition/dmet/_helpers/dmet_scf_guess.py b/tangelo/problem_decomposition/dmet/_helpers/dmet_scf_guess.py similarity index 97% rename from qsdk/problem_decomposition/dmet/_helpers/dmet_scf_guess.py rename to tangelo/problem_decomposition/dmet/_helpers/dmet_scf_guess.py index 3c6bb3af9..2bd3c0d92 100644 --- a/qsdk/problem_decomposition/dmet/_helpers/dmet_scf_guess.py +++ b/tangelo/problem_decomposition/dmet/_helpers/dmet_scf_guess.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/problem_decomposition/dmet/dmet_problem_decomposition.py b/tangelo/problem_decomposition/dmet/dmet_problem_decomposition.py similarity index 98% rename from qsdk/problem_decomposition/dmet/dmet_problem_decomposition.py rename to tangelo/problem_decomposition/dmet/dmet_problem_decomposition.py index bee366160..ab4bc7ad9 100644 --- a/qsdk/problem_decomposition/dmet/dmet_problem_decomposition.py +++ b/tangelo/problem_decomposition/dmet/dmet_problem_decomposition.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -21,12 +21,12 @@ import scipy import warnings -from qsdk.problem_decomposition.dmet import _helpers as helpers -from qsdk.problem_decomposition.problem_decomposition import ProblemDecomposition -from qsdk.problem_decomposition.electron_localization import iao_localization, meta_lowdin_localization -from qsdk.problem_decomposition.dmet.fragment import SecondQuantizedDMETFragment -from qsdk.algorithms import FCISolver, CCSDSolver, VQESolver -from qsdk.toolboxes.post_processing.mc_weeny_rdm_purification import mcweeny_purify_2rdm +from tangelo.problem_decomposition.dmet import _helpers as helpers +from tangelo.problem_decomposition.problem_decomposition import ProblemDecomposition +from tangelo.problem_decomposition.electron_localization import iao_localization, meta_lowdin_localization +from tangelo.problem_decomposition.dmet.fragment import SecondQuantizedDMETFragment +from tangelo.algorithms import FCISolver, CCSDSolver, VQESolver +from tangelo.toolboxes.post_processing.mc_weeny_rdm_purification import mcweeny_purify_2rdm class Localization(Enum): diff --git a/qsdk/problem_decomposition/dmet/fragment.py b/tangelo/problem_decomposition/dmet/fragment.py similarity index 94% rename from qsdk/problem_decomposition/dmet/fragment.py rename to tangelo/problem_decomposition/dmet/fragment.py index 30b5f0cd9..3c5e7dfbc 100644 --- a/qsdk/problem_decomposition/dmet/fragment.py +++ b/tangelo/problem_decomposition/dmet/fragment.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,8 +20,8 @@ import pyscf from pyscf import ao2mo -from qsdk.toolboxes.operators import FermionOperator -from qsdk.toolboxes.qubit_mappings.mapping_transform import get_fermion_operator +from tangelo.toolboxes.operators import FermionOperator +from tangelo.toolboxes.qubit_mappings.mapping_transform import get_fermion_operator @dataclass diff --git a/qsdk/problem_decomposition/electron_localization/__init__.py b/tangelo/problem_decomposition/electron_localization/__init__.py similarity index 92% rename from qsdk/problem_decomposition/electron_localization/__init__.py rename to tangelo/problem_decomposition/electron_localization/__init__.py index 2c5f0a05f..c71c6fd54 100644 --- a/qsdk/problem_decomposition/electron_localization/__init__.py +++ b/tangelo/problem_decomposition/electron_localization/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/problem_decomposition/electron_localization/iao_localization.py b/tangelo/problem_decomposition/electron_localization/iao_localization.py similarity index 99% rename from qsdk/problem_decomposition/electron_localization/iao_localization.py rename to tangelo/problem_decomposition/electron_localization/iao_localization.py index e511d7342..99cd3dc24 100644 --- a/qsdk/problem_decomposition/electron_localization/iao_localization.py +++ b/tangelo/problem_decomposition/electron_localization/iao_localization.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/problem_decomposition/electron_localization/meta_lowdin_localization.py b/tangelo/problem_decomposition/electron_localization/meta_lowdin_localization.py similarity index 95% rename from qsdk/problem_decomposition/electron_localization/meta_lowdin_localization.py rename to tangelo/problem_decomposition/electron_localization/meta_lowdin_localization.py index ba11b7774..0c0a30ad0 100644 --- a/qsdk/problem_decomposition/electron_localization/meta_lowdin_localization.py +++ b/tangelo/problem_decomposition/electron_localization/meta_lowdin_localization.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tangelo/problem_decomposition/oniom/__init__.py b/tangelo/problem_decomposition/oniom/__init__.py new file mode 100644 index 000000000..81a799660 --- /dev/null +++ b/tangelo/problem_decomposition/oniom/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/tangelo/problem_decomposition/oniom/_helpers/__init__.py b/tangelo/problem_decomposition/oniom/_helpers/__init__.py new file mode 100644 index 000000000..81a799660 --- /dev/null +++ b/tangelo/problem_decomposition/oniom/_helpers/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/qsdk/problem_decomposition/oniom/_helpers/helper_classes.py b/tangelo/problem_decomposition/oniom/_helpers/helper_classes.py similarity index 98% rename from qsdk/problem_decomposition/oniom/_helpers/helper_classes.py rename to tangelo/problem_decomposition/oniom/_helpers/helper_classes.py index 782d70748..5f8135bcf 100644 --- a/qsdk/problem_decomposition/oniom/_helpers/helper_classes.py +++ b/tangelo/problem_decomposition/oniom/_helpers/helper_classes.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,8 +20,8 @@ import numpy as np # Imports of electronic solvers and data structure -from qsdk.algorithms import CCSDSolver, FCISolver, VQESolver, MINDO3Solver -from qsdk import SecondQuantizedMolecule +from tangelo.algorithms import CCSDSolver, FCISolver, VQESolver, MINDO3Solver +from tangelo import SecondQuantizedMolecule class Fragment: diff --git a/qsdk/problem_decomposition/oniom/oniom_problem_decomposition.py b/tangelo/problem_decomposition/oniom/oniom_problem_decomposition.py similarity index 96% rename from qsdk/problem_decomposition/oniom/oniom_problem_decomposition.py rename to tangelo/problem_decomposition/oniom/oniom_problem_decomposition.py index 2fe492351..8999b9709 100644 --- a/qsdk/problem_decomposition/oniom/oniom_problem_decomposition.py +++ b/tangelo/problem_decomposition/oniom/oniom_problem_decomposition.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -32,8 +32,8 @@ # TODO: Supporting many (3+) layers of different accuracy. # TODO: Capping with CH3 or other functional groups. -from qsdk.problem_decomposition.problem_decomposition import ProblemDecomposition -from qsdk.toolboxes.molecular_computation.molecule import atom_string_to_list +from tangelo.problem_decomposition.problem_decomposition import ProblemDecomposition +from tangelo.toolboxes.molecular_computation.molecule import atom_string_to_list class ONIOMProblemDecomposition(ProblemDecomposition): diff --git a/qsdk/problem_decomposition/problem_decomposition.py b/tangelo/problem_decomposition/problem_decomposition.py similarity index 94% rename from qsdk/problem_decomposition/problem_decomposition.py rename to tangelo/problem_decomposition/problem_decomposition.py index 91b85d1cd..68a0c138c 100644 --- a/qsdk/problem_decomposition/problem_decomposition.py +++ b/tangelo/problem_decomposition/problem_decomposition.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tangelo/problem_decomposition/tests/__init__.py b/tangelo/problem_decomposition/tests/__init__.py new file mode 100644 index 000000000..81a799660 --- /dev/null +++ b/tangelo/problem_decomposition/tests/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/tangelo/problem_decomposition/tests/dmet/__init__.py b/tangelo/problem_decomposition/tests/dmet/__init__.py new file mode 100644 index 000000000..81a799660 --- /dev/null +++ b/tangelo/problem_decomposition/tests/dmet/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/qsdk/problem_decomposition/tests/dmet/data/test_dmet_oneshot_loop_core_rdm.txt b/tangelo/problem_decomposition/tests/dmet/data/test_dmet_oneshot_loop_core_rdm.txt similarity index 100% rename from qsdk/problem_decomposition/tests/dmet/data/test_dmet_oneshot_loop_core_rdm.txt rename to tangelo/problem_decomposition/tests/dmet/data/test_dmet_oneshot_loop_core_rdm.txt diff --git a/qsdk/problem_decomposition/tests/dmet/data/test_dmet_oneshot_loop_low_rdm.txt b/tangelo/problem_decomposition/tests/dmet/data/test_dmet_oneshot_loop_low_rdm.txt similarity index 100% rename from qsdk/problem_decomposition/tests/dmet/data/test_dmet_oneshot_loop_low_rdm.txt rename to tangelo/problem_decomposition/tests/dmet/data/test_dmet_oneshot_loop_low_rdm.txt diff --git a/qsdk/problem_decomposition/tests/dmet/data/test_dmet_orbitals.txt b/tangelo/problem_decomposition/tests/dmet/data/test_dmet_orbitals.txt similarity index 100% rename from qsdk/problem_decomposition/tests/dmet/data/test_dmet_orbitals.txt rename to tangelo/problem_decomposition/tests/dmet/data/test_dmet_orbitals.txt diff --git a/qsdk/problem_decomposition/tests/dmet/test_dmet.py b/tangelo/problem_decomposition/tests/dmet/test_dmet.py similarity index 95% rename from qsdk/problem_decomposition/tests/dmet/test_dmet.py rename to tangelo/problem_decomposition/tests/dmet/test_dmet.py index 947052b67..e6d753667 100644 --- a/qsdk/problem_decomposition/tests/dmet/test_dmet.py +++ b/tangelo/problem_decomposition/tests/dmet/test_dmet.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,11 +15,10 @@ import unittest import numpy as np -from qsdk.molecule_library import mol_H4_doublecation_minao, mol_H4_doublecation_321g, mol_H10_321g, mol_H10_minao -from qsdk.problem_decomposition import dmet -from qsdk.problem_decomposition.dmet.dmet_problem_decomposition import Localization, DMETProblemDecomposition -from qsdk.algorithms.variational import VQESolver -from qsdk.toolboxes.molecular_computation.rdms import matricize_2rdm +from tangelo.molecule_library import mol_H4_doublecation_minao, mol_H4_doublecation_321g, mol_H10_321g, mol_H10_minao +from tangelo.problem_decomposition.dmet.dmet_problem_decomposition import Localization, DMETProblemDecomposition +from tangelo.algorithms.variational import VQESolver +from tangelo.toolboxes.molecular_computation.rdms import matricize_2rdm class DMETProblemDecompositionTest(unittest.TestCase): diff --git a/qsdk/problem_decomposition/tests/dmet/test_dmet_fragment.py b/tangelo/problem_decomposition/tests/dmet/test_dmet_fragment.py similarity index 94% rename from qsdk/problem_decomposition/tests/dmet/test_dmet_fragment.py rename to tangelo/problem_decomposition/tests/dmet/test_dmet_fragment.py index 045ee11a0..ee81e4b2c 100644 --- a/qsdk/problem_decomposition/tests/dmet/test_dmet_fragment.py +++ b/tangelo/problem_decomposition/tests/dmet/test_dmet_fragment.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ import unittest from pyscf import gto -from qsdk.problem_decomposition.dmet._helpers.dmet_fragment import dmet_fragment_constructor +from tangelo.problem_decomposition.dmet._helpers.dmet_fragment import dmet_fragment_constructor class TestFragments(unittest.TestCase): diff --git a/qsdk/problem_decomposition/tests/dmet/test_dmet_oneshot_loop.py b/tangelo/problem_decomposition/tests/dmet/test_dmet_oneshot_loop.py similarity index 89% rename from qsdk/problem_decomposition/tests/dmet/test_dmet_oneshot_loop.py rename to tangelo/problem_decomposition/tests/dmet/test_dmet_oneshot_loop.py index 8e49b2336..8a26b06ab 100644 --- a/qsdk/problem_decomposition/tests/dmet/test_dmet_oneshot_loop.py +++ b/tangelo/problem_decomposition/tests/dmet/test_dmet_oneshot_loop.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,12 +19,12 @@ from pyscf import gto, scf import numpy as np -from qsdk.problem_decomposition.dmet._helpers.dmet_orbitals import dmet_orbitals -from qsdk.problem_decomposition.dmet._helpers.dmet_onerdm import dmet_low_rdm, dmet_fragment_rdm -from qsdk.problem_decomposition.dmet._helpers.dmet_bath import dmet_fragment_bath -from qsdk.problem_decomposition.dmet._helpers.dmet_scf_guess import dmet_fragment_guess -from qsdk.problem_decomposition.dmet._helpers.dmet_scf import dmet_fragment_scf -from qsdk.problem_decomposition.electron_localization import iao_localization +from tangelo.problem_decomposition.dmet._helpers.dmet_orbitals import dmet_orbitals +from tangelo.problem_decomposition.dmet._helpers.dmet_onerdm import dmet_low_rdm, dmet_fragment_rdm +from tangelo.problem_decomposition.dmet._helpers.dmet_bath import dmet_fragment_bath +from tangelo.problem_decomposition.dmet._helpers.dmet_scf_guess import dmet_fragment_guess +from tangelo.problem_decomposition.dmet._helpers.dmet_scf import dmet_fragment_scf +from tangelo.problem_decomposition.electron_localization import iao_localization path_file = os.path.dirname(os.path.abspath(__file__)) diff --git a/qsdk/problem_decomposition/tests/dmet/test_dmet_orbitals.py b/tangelo/problem_decomposition/tests/dmet/test_dmet_orbitals.py similarity index 92% rename from qsdk/problem_decomposition/tests/dmet/test_dmet_orbitals.py rename to tangelo/problem_decomposition/tests/dmet/test_dmet_orbitals.py index 189208444..4721b68f8 100644 --- a/qsdk/problem_decomposition/tests/dmet/test_dmet_orbitals.py +++ b/tangelo/problem_decomposition/tests/dmet/test_dmet_orbitals.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,8 +19,8 @@ from pyscf import gto, scf import numpy as np -from qsdk.problem_decomposition.dmet._helpers.dmet_orbitals import dmet_orbitals -from qsdk.problem_decomposition.electron_localization import iao_localization +from tangelo.problem_decomposition.dmet._helpers.dmet_orbitals import dmet_orbitals +from tangelo.problem_decomposition.electron_localization import iao_localization path_file = os.path.dirname(os.path.abspath(__file__)) diff --git a/qsdk/problem_decomposition/tests/dmet/test_dmet_vqe.py b/tangelo/problem_decomposition/tests/dmet/test_dmet_vqe.py similarity index 94% rename from qsdk/problem_decomposition/tests/dmet/test_dmet_vqe.py rename to tangelo/problem_decomposition/tests/dmet/test_dmet_vqe.py index 5cb4632e9..efaa5b595 100644 --- a/qsdk/problem_decomposition/tests/dmet/test_dmet_vqe.py +++ b/tangelo/problem_decomposition/tests/dmet/test_dmet_vqe.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,8 +15,8 @@ import unittest from copy import copy -from qsdk.molecule_library import mol_H4_minao -from qsdk.problem_decomposition.dmet.dmet_problem_decomposition import Localization, DMETProblemDecomposition +from tangelo.molecule_library import mol_H4_minao +from tangelo.problem_decomposition.dmet.dmet_problem_decomposition import Localization, DMETProblemDecomposition class DMETVQETest(unittest.TestCase): diff --git a/tangelo/problem_decomposition/tests/oniom/__init__.py b/tangelo/problem_decomposition/tests/oniom/__init__.py new file mode 100644 index 000000000..81a799660 --- /dev/null +++ b/tangelo/problem_decomposition/tests/oniom/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/qsdk/problem_decomposition/tests/oniom/test_oniom.py b/tangelo/problem_decomposition/tests/oniom/test_oniom.py similarity index 96% rename from qsdk/problem_decomposition/tests/oniom/test_oniom.py rename to tangelo/problem_decomposition/tests/oniom/test_oniom.py index 709cf1c10..1f3480c79 100644 --- a/qsdk/problem_decomposition/tests/oniom/test_oniom.py +++ b/tangelo/problem_decomposition/tests/oniom/test_oniom.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,9 +14,9 @@ import unittest -from qsdk.problem_decomposition.oniom.oniom_problem_decomposition import ONIOMProblemDecomposition -from qsdk.problem_decomposition.oniom._helpers.helper_classes import Fragment, Link -from qsdk.molecule_library import xyz_H4, xyz_PHE +from tangelo.problem_decomposition.oniom.oniom_problem_decomposition import ONIOMProblemDecomposition +from tangelo.problem_decomposition.oniom._helpers.helper_classes import Fragment, Link +from tangelo.molecule_library import xyz_H4, xyz_PHE class ONIOMTest(unittest.TestCase): diff --git a/tangelo/toolboxes/__init__.py b/tangelo/toolboxes/__init__.py new file mode 100644 index 000000000..81a799660 --- /dev/null +++ b/tangelo/toolboxes/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/tangelo/toolboxes/ansatz_generator/__init__.py b/tangelo/toolboxes/ansatz_generator/__init__.py new file mode 100644 index 000000000..81a799660 --- /dev/null +++ b/tangelo/toolboxes/ansatz_generator/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/qsdk/toolboxes/ansatz_generator/_general_unitary_cc.py b/tangelo/toolboxes/ansatz_generator/_general_unitary_cc.py similarity index 99% rename from qsdk/toolboxes/ansatz_generator/_general_unitary_cc.py rename to tangelo/toolboxes/ansatz_generator/_general_unitary_cc.py index 4c91ad489..67534a2ec 100644 --- a/qsdk/toolboxes/ansatz_generator/_general_unitary_cc.py +++ b/tangelo/toolboxes/ansatz_generator/_general_unitary_cc.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import numpy as np import itertools -from qsdk.toolboxes.operators import FermionOperator +from tangelo.toolboxes.operators import FermionOperator def hermitian_conjugate(terms): diff --git a/qsdk/toolboxes/ansatz_generator/_hea_circuit.py b/tangelo/toolboxes/ansatz_generator/_hea_circuit.py similarity index 95% rename from qsdk/toolboxes/ansatz_generator/_hea_circuit.py rename to tangelo/toolboxes/ansatz_generator/_hea_circuit.py index 4b3b228d6..c0d313e2f 100644 --- a/qsdk/toolboxes/ansatz_generator/_hea_circuit.py +++ b/tangelo/toolboxes/ansatz_generator/_hea_circuit.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ """Module to create Hardware Efficient Ansatze (HEA) circuit with n_layers.""" -from qsdk.backendbuddy import Circuit, Gate +from tangelo.backendbuddy import Circuit, Gate def rotation_circuit(n_qubits, rot_type="euler"): diff --git a/qsdk/toolboxes/ansatz_generator/_unitary_cc_openshell.py b/tangelo/toolboxes/ansatz_generator/_unitary_cc_openshell.py similarity index 99% rename from qsdk/toolboxes/ansatz_generator/_unitary_cc_openshell.py rename to tangelo/toolboxes/ansatz_generator/_unitary_cc_openshell.py index 5ef5a7700..11c009459 100644 --- a/qsdk/toolboxes/ansatz_generator/_unitary_cc_openshell.py +++ b/tangelo/toolboxes/ansatz_generator/_unitary_cc_openshell.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -33,7 +33,7 @@ from openfermion.utils import down_index, up_index -from qsdk.toolboxes.operators import FermionOperator +from tangelo.toolboxes.operators import FermionOperator def uccsd_openshell_paramsize(n_spinorbitals, n_alpha_electrons, n_beta_electrons): diff --git a/qsdk/toolboxes/ansatz_generator/_unitary_cc_paired.py b/tangelo/toolboxes/ansatz_generator/_unitary_cc_paired.py similarity index 94% rename from qsdk/toolboxes/ansatz_generator/_unitary_cc_paired.py rename to tangelo/toolboxes/ansatz_generator/_unitary_cc_paired.py index 532029aa7..45c280d53 100644 --- a/qsdk/toolboxes/ansatz_generator/_unitary_cc_paired.py +++ b/tangelo/toolboxes/ansatz_generator/_unitary_cc_paired.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,8 +16,8 @@ the UpCCGSD ansatz. """ -from qsdk.toolboxes.ansatz_generator._general_unitary_cc import get_spin_ordered -from qsdk.toolboxes.operators import FermionOperator +from tangelo.toolboxes.ansatz_generator._general_unitary_cc import get_spin_ordered +from tangelo.toolboxes.operators import FermionOperator def get_upccgsd(n_orbs, values, up_then_down=False, anti_hermitian=True): diff --git a/qsdk/toolboxes/ansatz_generator/adapt_ansatz.py b/tangelo/toolboxes/ansatz_generator/adapt_ansatz.py similarity index 94% rename from qsdk/toolboxes/ansatz_generator/adapt_ansatz.py rename to tangelo/toolboxes/ansatz_generator/adapt_ansatz.py index 87f61f76e..ffcce168c 100644 --- a/qsdk/toolboxes/ansatz_generator/adapt_ansatz.py +++ b/tangelo/toolboxes/ansatz_generator/adapt_ansatz.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,11 +16,11 @@ import math -from qsdk.backendbuddy import Circuit -from qsdk.toolboxes.qubit_mappings.statevector_mapping import get_reference_circuit -from qsdk.toolboxes.ansatz_generator.ansatz_utils import pauliword_to_circuit -from qsdk.toolboxes.ansatz_generator.ansatz import Ansatz -from qsdk.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping +from tangelo.backendbuddy import Circuit +from tangelo.toolboxes.qubit_mappings.statevector_mapping import get_reference_circuit +from tangelo.toolboxes.ansatz_generator.ansatz_utils import pauliword_to_circuit +from tangelo.toolboxes.ansatz_generator.ansatz import Ansatz +from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping class ADAPTAnsatz(Ansatz): diff --git a/qsdk/toolboxes/ansatz_generator/ansatz.py b/tangelo/toolboxes/ansatz_generator/ansatz.py similarity index 97% rename from qsdk/toolboxes/ansatz_generator/ansatz.py rename to tangelo/toolboxes/ansatz_generator/ansatz.py index 8d6e464a7..6d315d484 100644 --- a/qsdk/toolboxes/ansatz_generator/ansatz.py +++ b/tangelo/toolboxes/ansatz_generator/ansatz.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/toolboxes/ansatz_generator/ansatz_utils.py b/tangelo/toolboxes/ansatz_generator/ansatz_utils.py similarity index 95% rename from qsdk/toolboxes/ansatz_generator/ansatz_utils.py rename to tangelo/toolboxes/ansatz_generator/ansatz_utils.py index 560476a26..da049dc72 100644 --- a/qsdk/toolboxes/ansatz_generator/ansatz_utils.py +++ b/tangelo/toolboxes/ansatz_generator/ansatz_utils.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ """ import numpy as np -from qsdk.backendbuddy import Circuit, Gate +from tangelo.backendbuddy import Circuit, Gate def pauli_op_to_gate(index, op, inverse=False): diff --git a/qsdk/toolboxes/ansatz_generator/fermionic_operators.py b/tangelo/toolboxes/ansatz_generator/fermionic_operators.py similarity index 96% rename from qsdk/toolboxes/ansatz_generator/fermionic_operators.py rename to tangelo/toolboxes/ansatz_generator/fermionic_operators.py index ada0228bc..298787916 100644 --- a/qsdk/toolboxes/ansatz_generator/fermionic_operators.py +++ b/tangelo/toolboxes/ansatz_generator/fermionic_operators.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -21,8 +21,8 @@ properties. """ -from qsdk.toolboxes.ansatz_generator._general_unitary_cc import get_spin_ordered -from qsdk.toolboxes.operators import normal_ordered, list_to_fermionoperator +from tangelo.toolboxes.ansatz_generator._general_unitary_cc import get_spin_ordered +from tangelo.toolboxes.operators import normal_ordered, list_to_fermionoperator def number_operator(n_orbs, up_then_down=False): diff --git a/qsdk/toolboxes/ansatz_generator/hea.py b/tangelo/toolboxes/ansatz_generator/hea.py similarity index 96% rename from qsdk/toolboxes/ansatz_generator/hea.py rename to tangelo/toolboxes/ansatz_generator/hea.py index db2ab39fc..920b6895d 100644 --- a/qsdk/toolboxes/ansatz_generator/hea.py +++ b/tangelo/toolboxes/ansatz_generator/hea.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -21,9 +21,9 @@ from .ansatz import Ansatz from ._hea_circuit import construct_hea_circuit -from qsdk.toolboxes.qubit_mappings.mapping_transform import get_qubit_number -from qsdk.toolboxes.qubit_mappings.statevector_mapping import get_reference_circuit -from qsdk.backendbuddy import Circuit +from tangelo.toolboxes.qubit_mappings.mapping_transform import get_qubit_number +from tangelo.toolboxes.qubit_mappings.statevector_mapping import get_reference_circuit +from tangelo.backendbuddy import Circuit class HEA(Ansatz): diff --git a/qsdk/toolboxes/ansatz_generator/penalty_terms.py b/tangelo/toolboxes/ansatz_generator/penalty_terms.py similarity index 96% rename from qsdk/toolboxes/ansatz_generator/penalty_terms.py rename to tangelo/toolboxes/ansatz_generator/penalty_terms.py index 63d55d2cd..7996e7a9f 100644 --- a/qsdk/toolboxes/ansatz_generator/penalty_terms.py +++ b/tangelo/toolboxes/ansatz_generator/penalty_terms.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,8 +23,8 @@ Variational Quantum Eigensolver" https://arxiv.org/abs/1910.05168. """ -from qsdk.toolboxes.ansatz_generator.fermionic_operators import number_operator_list, spinz_operator_list, spin2_operator_list -from qsdk.toolboxes.operators import FermionOperator, squared_normal_ordered +from tangelo.toolboxes.ansatz_generator.fermionic_operators import number_operator_list, spinz_operator_list, spin2_operator_list +from tangelo.toolboxes.operators import FermionOperator, squared_normal_ordered def number_operator_penalty(n_orbs, n_electrons, mu=1, up_then_down=False): diff --git a/qsdk/toolboxes/ansatz_generator/rucc.py b/tangelo/toolboxes/ansatz_generator/rucc.py similarity index 98% rename from qsdk/toolboxes/ansatz_generator/rucc.py rename to tangelo/toolboxes/ansatz_generator/rucc.py index 2c2df90de..b90d94d6d 100644 --- a/qsdk/toolboxes/ansatz_generator/rucc.py +++ b/tangelo/toolboxes/ansatz_generator/rucc.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ import numpy as np -from qsdk.backendbuddy import Circuit, Gate +from tangelo.backendbuddy import Circuit, Gate from .ansatz import Ansatz diff --git a/tangelo/toolboxes/ansatz_generator/tests/__init__.py b/tangelo/toolboxes/ansatz_generator/tests/__init__.py new file mode 100644 index 000000000..81a799660 --- /dev/null +++ b/tangelo/toolboxes/ansatz_generator/tests/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/qsdk/toolboxes/ansatz_generator/tests/test_adapt_ansatz.py b/tangelo/toolboxes/ansatz_generator/tests/test_adapt_ansatz.py similarity index 87% rename from qsdk/toolboxes/ansatz_generator/tests/test_adapt_ansatz.py rename to tangelo/toolboxes/ansatz_generator/tests/test_adapt_ansatz.py index 37d6fd820..9ee77831e 100644 --- a/qsdk/toolboxes/ansatz_generator/tests/test_adapt_ansatz.py +++ b/tangelo/toolboxes/ansatz_generator/tests/test_adapt_ansatz.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,9 +15,9 @@ import unittest import math -from qsdk.toolboxes.ansatz_generator.adapt_ansatz import ADAPTAnsatz -from qsdk.toolboxes.operators import FermionOperator -from qsdk.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping +from tangelo.toolboxes.ansatz_generator.adapt_ansatz import ADAPTAnsatz +from tangelo.toolboxes.operators import FermionOperator +from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping f_op = FermionOperator("2^ 3^ 0 1") - FermionOperator("0^ 1^ 2 3") qu_op = fermion_to_qubit_mapping(f_op, "jw") diff --git a/qsdk/toolboxes/ansatz_generator/tests/test_fermionic_operators.py b/tangelo/toolboxes/ansatz_generator/tests/test_fermionic_operators.py similarity index 95% rename from qsdk/toolboxes/ansatz_generator/tests/test_fermionic_operators.py rename to tangelo/toolboxes/ansatz_generator/tests/test_fermionic_operators.py index 1749bc2d9..7085c08eb 100644 --- a/qsdk/toolboxes/ansatz_generator/tests/test_fermionic_operators.py +++ b/tangelo/toolboxes/ansatz_generator/tests/test_fermionic_operators.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,11 +14,11 @@ import unittest -from qsdk.backendbuddy import Simulator -from qsdk.molecule_library import mol_H2_sto3g, mol_H4_sto3g -from qsdk.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping -from qsdk.toolboxes.qubit_mappings.statevector_mapping import get_reference_circuit, get_vector, vector_to_circuit -from qsdk.toolboxes.ansatz_generator.fermionic_operators import number_operator, spinz_operator, spin2_operator +from tangelo.backendbuddy import Simulator +from tangelo.molecule_library import mol_H2_sto3g, mol_H4_sto3g +from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping +from tangelo.toolboxes.qubit_mappings.statevector_mapping import get_reference_circuit, get_vector, vector_to_circuit +from tangelo.toolboxes.ansatz_generator.fermionic_operators import number_operator, spinz_operator, spin2_operator # Initiate simulator sim = Simulator() diff --git a/qsdk/toolboxes/ansatz_generator/tests/test_general_unitary_cc.py b/tangelo/toolboxes/ansatz_generator/tests/test_general_unitary_cc.py similarity index 97% rename from qsdk/toolboxes/ansatz_generator/tests/test_general_unitary_cc.py rename to tangelo/toolboxes/ansatz_generator/tests/test_general_unitary_cc.py index 5b9304dce..750525927 100644 --- a/qsdk/toolboxes/ansatz_generator/tests/test_general_unitary_cc.py +++ b/tangelo/toolboxes/ansatz_generator/tests/test_general_unitary_cc.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ import unittest from scipy.special import binom -from qsdk.toolboxes.ansatz_generator._general_unitary_cc import * +from tangelo.toolboxes.ansatz_generator._general_unitary_cc import * class UCCGSDTest(unittest.TestCase): diff --git a/qsdk/toolboxes/ansatz_generator/tests/test_hea.py b/tangelo/toolboxes/ansatz_generator/tests/test_hea.py similarity index 93% rename from qsdk/toolboxes/ansatz_generator/tests/test_hea.py rename to tangelo/toolboxes/ansatz_generator/tests/test_hea.py index 58f8202d5..6467ec67e 100644 --- a/qsdk/toolboxes/ansatz_generator/tests/test_hea.py +++ b/tangelo/toolboxes/ansatz_generator/tests/test_hea.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,11 +15,11 @@ import unittest import numpy as np -from qsdk.molecule_library import mol_H2_sto3g, mol_H4_doublecation_minao -from qsdk.toolboxes.qubit_mappings import jordan_wigner -from qsdk.toolboxes.ansatz_generator.hea import HEA +from tangelo.molecule_library import mol_H2_sto3g, mol_H4_doublecation_minao +from tangelo.toolboxes.qubit_mappings import jordan_wigner +from tangelo.toolboxes.ansatz_generator.hea import HEA -from qsdk.backendbuddy import Simulator +from tangelo.backendbuddy import Simulator # Initiate simulator sim = Simulator() diff --git a/qsdk/toolboxes/ansatz_generator/tests/test_penalty_terms.py b/tangelo/toolboxes/ansatz_generator/tests/test_penalty_terms.py similarity index 96% rename from qsdk/toolboxes/ansatz_generator/tests/test_penalty_terms.py rename to tangelo/toolboxes/ansatz_generator/tests/test_penalty_terms.py index 120546319..ca4a64047 100644 --- a/qsdk/toolboxes/ansatz_generator/tests/test_penalty_terms.py +++ b/tangelo/toolboxes/ansatz_generator/tests/test_penalty_terms.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,11 +14,11 @@ import unittest -from qsdk.backendbuddy import Simulator -from qsdk.molecule_library import mol_H2_sto3g, mol_H4_sto3g -from qsdk.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping -from qsdk.toolboxes.qubit_mappings.statevector_mapping import get_reference_circuit, get_vector, vector_to_circuit -from qsdk.toolboxes.ansatz_generator.penalty_terms import number_operator_penalty, spin_operator_penalty, spin2_operator_penalty +from tangelo.backendbuddy import Simulator +from tangelo.molecule_library import mol_H2_sto3g, mol_H4_sto3g +from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping +from tangelo.toolboxes.qubit_mappings.statevector_mapping import get_reference_circuit, get_vector, vector_to_circuit +from tangelo.toolboxes.ansatz_generator.penalty_terms import number_operator_penalty, spin_operator_penalty, spin2_operator_penalty # Initiate simulator sim = Simulator() diff --git a/qsdk/toolboxes/ansatz_generator/tests/test_rucc.py b/tangelo/toolboxes/ansatz_generator/tests/test_rucc.py similarity index 96% rename from qsdk/toolboxes/ansatz_generator/tests/test_rucc.py rename to tangelo/toolboxes/ansatz_generator/tests/test_rucc.py index 7df52993f..3fa211ee9 100644 --- a/qsdk/toolboxes/ansatz_generator/tests/test_rucc.py +++ b/tangelo/toolboxes/ansatz_generator/tests/test_rucc.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ import unittest import numpy as np -from qsdk.toolboxes.ansatz_generator.rucc import RUCC +from tangelo.toolboxes.ansatz_generator.rucc import RUCC class RUCCTest(unittest.TestCase): diff --git a/qsdk/toolboxes/ansatz_generator/tests/test_uccsd.py b/tangelo/toolboxes/ansatz_generator/tests/test_uccsd.py similarity index 94% rename from qsdk/toolboxes/ansatz_generator/tests/test_uccsd.py rename to tangelo/toolboxes/ansatz_generator/tests/test_uccsd.py index 44ee09de1..654947ad1 100644 --- a/qsdk/toolboxes/ansatz_generator/tests/test_uccsd.py +++ b/tangelo/toolboxes/ansatz_generator/tests/test_uccsd.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,11 +15,10 @@ import unittest import numpy as np -from qsdk.molecule_library import mol_H2_sto3g, mol_H4_sto3g, mol_H4_doublecation_minao, mol_H4_cation_sto3g -from qsdk.toolboxes.qubit_mappings import jordan_wigner -from qsdk.toolboxes.ansatz_generator.uccsd import UCCSD - -from qsdk.backendbuddy import Simulator +from tangelo.molecule_library import mol_H2_sto3g, mol_H4_sto3g, mol_H4_doublecation_minao, mol_H4_cation_sto3g +from tangelo.toolboxes.qubit_mappings import jordan_wigner +from tangelo.toolboxes.ansatz_generator.uccsd import UCCSD +from tangelo.backendbuddy import Simulator class UCCSDTest(unittest.TestCase): diff --git a/qsdk/toolboxes/ansatz_generator/tests/test_upccgsd.py b/tangelo/toolboxes/ansatz_generator/tests/test_upccgsd.py similarity index 94% rename from qsdk/toolboxes/ansatz_generator/tests/test_upccgsd.py rename to tangelo/toolboxes/ansatz_generator/tests/test_upccgsd.py index ed4747860..8d3a706d3 100644 --- a/qsdk/toolboxes/ansatz_generator/tests/test_upccgsd.py +++ b/tangelo/toolboxes/ansatz_generator/tests/test_upccgsd.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,11 +15,10 @@ import unittest import numpy as np -from qsdk.molecule_library import mol_H2_sto3g, mol_H4_doublecation_minao, mol_H4_cation_sto3g -from qsdk.toolboxes.qubit_mappings import jordan_wigner -from qsdk.toolboxes.ansatz_generator.upccgsd import UpCCGSD - -from qsdk.backendbuddy import Simulator +from tangelo.molecule_library import mol_H2_sto3g, mol_H4_doublecation_minao, mol_H4_cation_sto3g +from tangelo.toolboxes.qubit_mappings import jordan_wigner +from tangelo.toolboxes.ansatz_generator.upccgsd import UpCCGSD +from tangelo.backendbuddy import Simulator class UpCCGSDTest(unittest.TestCase): diff --git a/qsdk/toolboxes/ansatz_generator/tests/test_variational_circuit.py b/tangelo/toolboxes/ansatz_generator/tests/test_variational_circuit.py similarity index 92% rename from qsdk/toolboxes/ansatz_generator/tests/test_variational_circuit.py rename to tangelo/toolboxes/ansatz_generator/tests/test_variational_circuit.py index a0dc392dd..782738d4f 100644 --- a/qsdk/toolboxes/ansatz_generator/tests/test_variational_circuit.py +++ b/tangelo/toolboxes/ansatz_generator/tests/test_variational_circuit.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,8 +15,8 @@ import unittest import numpy as np -from qsdk.backendbuddy import Gate, Circuit -from qsdk.toolboxes.ansatz_generator.variational_circuit import VariationalCircuitAnsatz +from tangelo.backendbuddy import Gate, Circuit +from tangelo.toolboxes.ansatz_generator.variational_circuit import VariationalCircuitAnsatz # UCC1 hard coding circuit. Simple example not relying on import Ansatz. diff --git a/qsdk/toolboxes/ansatz_generator/uccsd.py b/tangelo/toolboxes/ansatz_generator/uccsd.py similarity index 97% rename from qsdk/toolboxes/ansatz_generator/uccsd.py rename to tangelo/toolboxes/ansatz_generator/uccsd.py index d65bc18e2..0bcd6ff10 100644 --- a/qsdk/toolboxes/ansatz_generator/uccsd.py +++ b/tangelo/toolboxes/ansatz_generator/uccsd.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -33,13 +33,13 @@ from pyscf import mp from openfermion.circuits import uccsd_singlet_generator -from qsdk.backendbuddy import Circuit +from tangelo.backendbuddy import Circuit from .ansatz import Ansatz from .ansatz_utils import pauliword_to_circuit from ._unitary_cc_openshell import uccsd_openshell_paramsize, uccsd_openshell_generator -from qsdk.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping -from qsdk.toolboxes.qubit_mappings.statevector_mapping import get_reference_circuit +from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping +from tangelo.toolboxes.qubit_mappings.statevector_mapping import get_reference_circuit class UCCSD(Ansatz): diff --git a/qsdk/toolboxes/ansatz_generator/upccgsd.py b/tangelo/toolboxes/ansatz_generator/upccgsd.py similarity index 97% rename from qsdk/toolboxes/ansatz_generator/upccgsd.py rename to tangelo/toolboxes/ansatz_generator/upccgsd.py index ce61ddbce..e5f06bfdd 100644 --- a/qsdk/toolboxes/ansatz_generator/upccgsd.py +++ b/tangelo/toolboxes/ansatz_generator/upccgsd.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -26,13 +26,13 @@ import numpy as np -from qsdk.backendbuddy import Circuit +from tangelo.backendbuddy import Circuit from .ansatz import Ansatz from .ansatz_utils import pauliword_to_circuit from ._unitary_cc_paired import get_upccgsd -from qsdk.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping -from qsdk.toolboxes.qubit_mappings.statevector_mapping import get_reference_circuit +from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping +from tangelo.toolboxes.qubit_mappings.statevector_mapping import get_reference_circuit class UpCCGSD(Ansatz): diff --git a/qsdk/toolboxes/ansatz_generator/variational_circuit.py b/tangelo/toolboxes/ansatz_generator/variational_circuit.py similarity index 94% rename from qsdk/toolboxes/ansatz_generator/variational_circuit.py rename to tangelo/toolboxes/ansatz_generator/variational_circuit.py index 6e25ea7dd..d5764fa1c 100644 --- a/qsdk/toolboxes/ansatz_generator/variational_circuit.py +++ b/tangelo/toolboxes/ansatz_generator/variational_circuit.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,13 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" This module defines an ansatz class to wrap up a custom qsdk.backendbuddy +""" This module defines an ansatz class to wrap up a custom tangelo.backendbuddy circuit. """ import numpy as np -from qsdk.toolboxes.ansatz_generator.ansatz import Ansatz +from tangelo.toolboxes.ansatz_generator.ansatz import Ansatz class VariationalCircuitAnsatz(Ansatz): diff --git a/qsdk/toolboxes/measurements/__init__.py b/tangelo/toolboxes/measurements/__init__.py similarity index 93% rename from qsdk/toolboxes/measurements/__init__.py rename to tangelo/toolboxes/measurements/__init__.py index c8de58c2d..fa5894672 100644 --- a/qsdk/toolboxes/measurements/__init__.py +++ b/tangelo/toolboxes/measurements/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/toolboxes/measurements/estimate_measurements.py b/tangelo/toolboxes/measurements/estimate_measurements.py similarity index 97% rename from qsdk/toolboxes/measurements/estimate_measurements.py rename to tangelo/toolboxes/measurements/estimate_measurements.py index 62fd14381..ab020136e 100644 --- a/qsdk/toolboxes/measurements/estimate_measurements.py +++ b/tangelo/toolboxes/measurements/estimate_measurements.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/toolboxes/measurements/qubit_terms_grouping.py b/tangelo/toolboxes/measurements/qubit_terms_grouping.py similarity index 97% rename from qsdk/toolboxes/measurements/qubit_terms_grouping.py rename to tangelo/toolboxes/measurements/qubit_terms_grouping.py index 7646a11dd..d6e6e6cbf 100644 --- a/qsdk/toolboxes/measurements/qubit_terms_grouping.py +++ b/tangelo/toolboxes/measurements/qubit_terms_grouping.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import warnings from openfermion.measurements import group_into_tensor_product_basis_sets -from qsdk.backendbuddy import Simulator +from tangelo.backendbuddy import Simulator def group_qwc(qb_ham, seed=None): diff --git a/tangelo/toolboxes/measurements/tests/__init__.py b/tangelo/toolboxes/measurements/tests/__init__.py new file mode 100644 index 000000000..81a799660 --- /dev/null +++ b/tangelo/toolboxes/measurements/tests/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/qsdk/toolboxes/measurements/tests/data b/tangelo/toolboxes/measurements/tests/data similarity index 100% rename from qsdk/toolboxes/measurements/tests/data rename to tangelo/toolboxes/measurements/tests/data diff --git a/qsdk/toolboxes/measurements/tests/test_measurements.py b/tangelo/toolboxes/measurements/tests/test_measurements.py similarity index 92% rename from qsdk/toolboxes/measurements/tests/test_measurements.py rename to tangelo/toolboxes/measurements/tests/test_measurements.py index e817e978a..2c1acba31 100644 --- a/qsdk/toolboxes/measurements/tests/test_measurements.py +++ b/tangelo/toolboxes/measurements/tests/test_measurements.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,11 +15,11 @@ import unittest import os -from qsdk.helpers.utils import default_simulator -from qsdk.backendbuddy import translator, Simulator, Circuit -from qsdk.backendbuddy.helpers import string_ham_to_of, measurement_basis_gates -from qsdk.toolboxes.operators import QubitOperator -from qsdk.toolboxes.measurements import get_measurement_estimate +from tangelo.helpers.utils import default_simulator +from tangelo.backendbuddy import translator, Simulator, Circuit +from tangelo.backendbuddy.helpers import string_ham_to_of, measurement_basis_gates +from tangelo.toolboxes.operators import QubitOperator +from tangelo.toolboxes.measurements import get_measurement_estimate path_data = os.path.dirname(os.path.abspath(__file__)) + '/data' diff --git a/qsdk/toolboxes/measurements/tests/test_qubit_terms_grouping.py b/tangelo/toolboxes/measurements/tests/test_qubit_terms_grouping.py similarity index 93% rename from qsdk/toolboxes/measurements/tests/test_qubit_terms_grouping.py rename to tangelo/toolboxes/measurements/tests/test_qubit_terms_grouping.py index cf4a897c9..8c687947c 100644 --- a/qsdk/toolboxes/measurements/tests/test_qubit_terms_grouping.py +++ b/tangelo/toolboxes/measurements/tests/test_qubit_terms_grouping.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,9 +15,9 @@ import unittest import os -from qsdk.backendbuddy import translator, Simulator, Circuit -from qsdk.helpers import string_ham_to_of, measurement_basis_gates -from qsdk.toolboxes.measurements import group_qwc, exp_value_from_measurement_bases +from tangelo.backendbuddy import translator, Simulator, Circuit +from tangelo.helpers import string_ham_to_of, measurement_basis_gates +from tangelo.toolboxes.measurements import group_qwc, exp_value_from_measurement_bases path_data = os.path.dirname(os.path.abspath(__file__)) + '/data' diff --git a/tangelo/toolboxes/molecular_computation/__init__.py b/tangelo/toolboxes/molecular_computation/__init__.py new file mode 100644 index 000000000..81a799660 --- /dev/null +++ b/tangelo/toolboxes/molecular_computation/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/qsdk/toolboxes/molecular_computation/frozen_orbitals.py b/tangelo/toolboxes/molecular_computation/frozen_orbitals.py similarity index 98% rename from qsdk/toolboxes/molecular_computation/frozen_orbitals.py rename to tangelo/toolboxes/molecular_computation/frozen_orbitals.py index 0e326366e..a2e711694 100644 --- a/qsdk/toolboxes/molecular_computation/frozen_orbitals.py +++ b/tangelo/toolboxes/molecular_computation/frozen_orbitals.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/toolboxes/molecular_computation/molecule.py b/tangelo/toolboxes/molecular_computation/molecule.py similarity index 98% rename from qsdk/toolboxes/molecular_computation/molecule.py rename to tangelo/toolboxes/molecular_computation/molecule.py index 6fc2cc667..fd962e95d 100644 --- a/qsdk/toolboxes/molecular_computation/molecule.py +++ b/tangelo/toolboxes/molecular_computation/molecule.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -25,8 +25,8 @@ from openfermionpyscf import run_pyscf from openfermion.ops.representations.interaction_operator import get_active_space_integrals -from qsdk.toolboxes.molecular_computation.frozen_orbitals import get_frozen_core -from qsdk.toolboxes.qubit_mappings.mapping_transform import get_fermion_operator +from tangelo.toolboxes.molecular_computation.frozen_orbitals import get_frozen_core +from tangelo.toolboxes.qubit_mappings.mapping_transform import get_fermion_operator def atom_string_to_list(atom_string): diff --git a/qsdk/toolboxes/molecular_computation/rdms.py b/tangelo/toolboxes/molecular_computation/rdms.py similarity index 100% rename from qsdk/toolboxes/molecular_computation/rdms.py rename to tangelo/toolboxes/molecular_computation/rdms.py diff --git a/tangelo/toolboxes/molecular_computation/tests/__init__.py b/tangelo/toolboxes/molecular_computation/tests/__init__.py new file mode 100644 index 000000000..81a799660 --- /dev/null +++ b/tangelo/toolboxes/molecular_computation/tests/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/qsdk/toolboxes/molecular_computation/tests/test_frozen_orbitals.py b/tangelo/toolboxes/molecular_computation/tests/test_frozen_orbitals.py similarity index 86% rename from qsdk/toolboxes/molecular_computation/tests/test_frozen_orbitals.py rename to tangelo/toolboxes/molecular_computation/tests/test_frozen_orbitals.py index 42e3db23f..89d1b6f6a 100644 --- a/qsdk/toolboxes/molecular_computation/tests/test_frozen_orbitals.py +++ b/tangelo/toolboxes/molecular_computation/tests/test_frozen_orbitals.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,8 +14,8 @@ import unittest -from qsdk.molecule_library import mol_H2O_321g -from qsdk.toolboxes.molecular_computation.frozen_orbitals import get_frozen_core, get_orbitals_excluding_homo_lumo +from tangelo.molecule_library import mol_H2O_321g +from tangelo.toolboxes.molecular_computation.frozen_orbitals import get_frozen_core, get_orbitals_excluding_homo_lumo class FrozenOrbitalsTest(unittest.TestCase): diff --git a/qsdk/toolboxes/molecular_computation/tests/test_molecule.py b/tangelo/toolboxes/molecular_computation/tests/test_molecule.py similarity index 96% rename from qsdk/toolboxes/molecular_computation/tests/test_molecule.py rename to tangelo/toolboxes/molecular_computation/tests/test_molecule.py index aca1fef39..d73aa262d 100644 --- a/qsdk/toolboxes/molecular_computation/tests/test_molecule.py +++ b/tangelo/toolboxes/molecular_computation/tests/test_molecule.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,9 +14,9 @@ import unittest -from qsdk import SecondQuantizedMolecule -from qsdk.molecule_library import mol_H2_sto3g -from qsdk.toolboxes.molecular_computation.molecule import atom_string_to_list +from tangelo import SecondQuantizedMolecule +from tangelo.molecule_library import mol_H2_sto3g +from tangelo.toolboxes.molecular_computation.molecule import atom_string_to_list H2_list = [("H", (0., 0., 0.)), ("H", (0., 0., 0.7414))] diff --git a/qsdk/toolboxes/operators/__init__.py b/tangelo/toolboxes/operators/__init__.py similarity index 93% rename from qsdk/toolboxes/operators/__init__.py rename to tangelo/toolboxes/operators/__init__.py index 116472dd8..3f4905d3e 100644 --- a/qsdk/toolboxes/operators/__init__.py +++ b/tangelo/toolboxes/operators/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/toolboxes/operators/operators.py b/tangelo/toolboxes/operators/operators.py similarity index 97% rename from qsdk/toolboxes/operators/operators.py rename to tangelo/toolboxes/operators/operators.py index e7dd1413a..04ed7c792 100644 --- a/qsdk/toolboxes/operators/operators.py +++ b/tangelo/toolboxes/operators/operators.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -114,7 +114,7 @@ def normal_ordered(fe_op): # Obtain normal ordered fermionic operator as list of terms norm_ord_terms = openfermion.transforms.normal_ordered(fe_op).terms - # Regeneratore full operator using class of qsdk.toolboxes.operators.FermionicOperator + # Regeneratore full operator using class of tangelo.toolboxes.operators.FermionicOperator norm_ord_fe_op = FermionOperator() for term in norm_ord_terms: norm_ord_fe_op += FermionOperator(term, norm_ord_terms[term]) diff --git a/tangelo/toolboxes/operators/tests/__init__.py b/tangelo/toolboxes/operators/tests/__init__.py new file mode 100644 index 000000000..81a799660 --- /dev/null +++ b/tangelo/toolboxes/operators/tests/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/qsdk/toolboxes/operators/tests/test_operators.py b/tangelo/toolboxes/operators/tests/test_operators.py similarity index 94% rename from qsdk/toolboxes/operators/tests/test_operators.py rename to tangelo/toolboxes/operators/tests/test_operators.py index e3222cf81..9034c7a8a 100644 --- a/qsdk/toolboxes/operators/tests/test_operators.py +++ b/tangelo/toolboxes/operators/tests/test_operators.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ import unittest -from qsdk.toolboxes.operators import QubitHamiltonian, QubitOperator, count_qubits, qubitop_to_qubitham +from tangelo.toolboxes.operators import QubitHamiltonian, QubitOperator, count_qubits, qubitop_to_qubitham class OperatorsUtilitiesTest(unittest.TestCase): diff --git a/qsdk/toolboxes/post_processing/__init__.py b/tangelo/toolboxes/post_processing/__init__.py similarity index 92% rename from qsdk/toolboxes/post_processing/__init__.py rename to tangelo/toolboxes/post_processing/__init__.py index 36b5691e7..b73851e5e 100644 --- a/qsdk/toolboxes/post_processing/__init__.py +++ b/tangelo/toolboxes/post_processing/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/toolboxes/post_processing/bootstrapping.py b/tangelo/toolboxes/post_processing/bootstrapping.py similarity index 97% rename from qsdk/toolboxes/post_processing/bootstrapping.py rename to tangelo/toolboxes/post_processing/bootstrapping.py index 20401a97e..323742ddb 100644 --- a/qsdk/toolboxes/post_processing/bootstrapping.py +++ b/tangelo/toolboxes/post_processing/bootstrapping.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/toolboxes/post_processing/mc_weeny_rdm_purification.py b/tangelo/toolboxes/post_processing/mc_weeny_rdm_purification.py similarity index 98% rename from qsdk/toolboxes/post_processing/mc_weeny_rdm_purification.py rename to tangelo/toolboxes/post_processing/mc_weeny_rdm_purification.py index 9a4c3e3fd..f69e3fd5c 100644 --- a/qsdk/toolboxes/post_processing/mc_weeny_rdm_purification.py +++ b/tangelo/toolboxes/post_processing/mc_weeny_rdm_purification.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tangelo/toolboxes/post_processing/tests/__init__.py b/tangelo/toolboxes/post_processing/tests/__init__.py new file mode 100644 index 000000000..81a799660 --- /dev/null +++ b/tangelo/toolboxes/post_processing/tests/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/qsdk/toolboxes/post_processing/tests/data/rdm1_nw.npy b/tangelo/toolboxes/post_processing/tests/data/rdm1_nw.npy similarity index 100% rename from qsdk/toolboxes/post_processing/tests/data/rdm1_nw.npy rename to tangelo/toolboxes/post_processing/tests/data/rdm1_nw.npy diff --git a/qsdk/toolboxes/post_processing/tests/data/rdm1_nw_exact.npy b/tangelo/toolboxes/post_processing/tests/data/rdm1_nw_exact.npy similarity index 100% rename from qsdk/toolboxes/post_processing/tests/data/rdm1_nw_exact.npy rename to tangelo/toolboxes/post_processing/tests/data/rdm1_nw_exact.npy diff --git a/qsdk/toolboxes/post_processing/tests/data/rdm2_nw.npy b/tangelo/toolboxes/post_processing/tests/data/rdm2_nw.npy similarity index 100% rename from qsdk/toolboxes/post_processing/tests/data/rdm2_nw.npy rename to tangelo/toolboxes/post_processing/tests/data/rdm2_nw.npy diff --git a/qsdk/toolboxes/post_processing/tests/data/rdm2_nw_exact.npy b/tangelo/toolboxes/post_processing/tests/data/rdm2_nw_exact.npy similarity index 100% rename from qsdk/toolboxes/post_processing/tests/data/rdm2_nw_exact.npy rename to tangelo/toolboxes/post_processing/tests/data/rdm2_nw_exact.npy diff --git a/qsdk/toolboxes/post_processing/tests/data/rdm2_spin_pot7_exact.npy b/tangelo/toolboxes/post_processing/tests/data/rdm2_spin_pot7_exact.npy similarity index 100% rename from qsdk/toolboxes/post_processing/tests/data/rdm2_spin_pot7_exact.npy rename to tangelo/toolboxes/post_processing/tests/data/rdm2_spin_pot7_exact.npy diff --git a/qsdk/toolboxes/post_processing/tests/data/rdm2_spin_pot7_experimental.npy b/tangelo/toolboxes/post_processing/tests/data/rdm2_spin_pot7_experimental.npy similarity index 100% rename from qsdk/toolboxes/post_processing/tests/data/rdm2_spin_pot7_experimental.npy rename to tangelo/toolboxes/post_processing/tests/data/rdm2_spin_pot7_experimental.npy diff --git a/qsdk/toolboxes/post_processing/tests/test_mcweeny_purification.py b/tangelo/toolboxes/post_processing/tests/test_mcweeny_purification.py similarity index 95% rename from qsdk/toolboxes/post_processing/tests/test_mcweeny_purification.py rename to tangelo/toolboxes/post_processing/tests/test_mcweeny_purification.py index 1dadaf7c1..b76879284 100644 --- a/qsdk/toolboxes/post_processing/tests/test_mcweeny_purification.py +++ b/tangelo/toolboxes/post_processing/tests/test_mcweeny_purification.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ import os import numpy as np -from qsdk.toolboxes.post_processing import mcweeny_purify_2rdm +from tangelo.toolboxes.post_processing import mcweeny_purify_2rdm path_data = os.path.dirname(os.path.abspath(__file__)) + "/data" diff --git a/qsdk/toolboxes/qubit_mappings/__init__.py b/tangelo/toolboxes/qubit_mappings/__init__.py similarity index 93% rename from qsdk/toolboxes/qubit_mappings/__init__.py rename to tangelo/toolboxes/qubit_mappings/__init__.py index 7054c419b..fa0378cf8 100644 --- a/qsdk/toolboxes/qubit_mappings/__init__.py +++ b/tangelo/toolboxes/qubit_mappings/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/toolboxes/qubit_mappings/bravyi_kitaev.py b/tangelo/toolboxes/qubit_mappings/bravyi_kitaev.py similarity index 97% rename from qsdk/toolboxes/qubit_mappings/bravyi_kitaev.py rename to tangelo/toolboxes/qubit_mappings/bravyi_kitaev.py index 632c8cd84..a05692baf 100644 --- a/qsdk/toolboxes/qubit_mappings/bravyi_kitaev.py +++ b/tangelo/toolboxes/qubit_mappings/bravyi_kitaev.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/toolboxes/qubit_mappings/jordan_wigner.py b/tangelo/toolboxes/qubit_mappings/jordan_wigner.py similarity index 97% rename from qsdk/toolboxes/qubit_mappings/jordan_wigner.py rename to tangelo/toolboxes/qubit_mappings/jordan_wigner.py index d94b6dda7..d2ec217bd 100644 --- a/qsdk/toolboxes/qubit_mappings/jordan_wigner.py +++ b/tangelo/toolboxes/qubit_mappings/jordan_wigner.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/qsdk/toolboxes/qubit_mappings/mapping_transform.py b/tangelo/toolboxes/qubit_mappings/mapping_transform.py similarity index 96% rename from qsdk/toolboxes/qubit_mappings/mapping_transform.py rename to tangelo/toolboxes/qubit_mappings/mapping_transform.py index 4c43ffdb7..cbc5c8a5d 100644 --- a/qsdk/toolboxes/qubit_mappings/mapping_transform.py +++ b/tangelo/toolboxes/qubit_mappings/mapping_transform.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -24,8 +24,8 @@ from collections.abc import Iterable from openfermion import FermionOperator as ofFermionOperator -from qsdk.toolboxes.operators import FermionOperator, QubitOperator -from qsdk.toolboxes.qubit_mappings import jordan_wigner, bravyi_kitaev, symmetry_conserving_bravyi_kitaev +from tangelo.toolboxes.operators import FermionOperator, QubitOperator +from tangelo.toolboxes.qubit_mappings import jordan_wigner, bravyi_kitaev, symmetry_conserving_bravyi_kitaev available_mappings = {"JW", "BK", "SCBK"} diff --git a/qsdk/toolboxes/qubit_mappings/statevector_mapping.py b/tangelo/toolboxes/qubit_mappings/statevector_mapping.py similarity index 95% rename from qsdk/toolboxes/qubit_mappings/statevector_mapping.py rename to tangelo/toolboxes/qubit_mappings/statevector_mapping.py index b4ee73944..65919d0a9 100644 --- a/qsdk/toolboxes/qubit_mappings/statevector_mapping.py +++ b/tangelo/toolboxes/qubit_mappings/statevector_mapping.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import numpy as np import warnings -from qsdk.backendbuddy import Gate, Circuit +from tangelo.backendbuddy import Gate, Circuit from openfermion.transforms import bravyi_kitaev_code @@ -113,7 +113,7 @@ def vector_to_circuit(vector): vector (numpy array of int): occupation vector. Returns: - Circuit: instance of qsdk.backendbuddy Circuit class. + Circuit: instance of tangelo.backendbuddy Circuit class. """ n_qubits = len(vector) @@ -142,7 +142,7 @@ def get_reference_circuit(n_spinorbitals, n_electrons, mapping, up_then_down=Fal spin (int): 2*S = n_alpha - n_beta. Returns: - Circuit: instance of qsdk.backendbuddy Circuit class. + Circuit: instance of tangelo.backendbuddy Circuit class. """ vector = get_vector(n_spinorbitals, n_electrons, mapping, up_then_down=up_then_down, spin=spin) circuit = vector_to_circuit(vector) diff --git a/qsdk/toolboxes/qubit_mappings/symmetry_conserving_bravyi_kitaev.py b/tangelo/toolboxes/qubit_mappings/symmetry_conserving_bravyi_kitaev.py similarity index 99% rename from qsdk/toolboxes/qubit_mappings/symmetry_conserving_bravyi_kitaev.py rename to tangelo/toolboxes/qubit_mappings/symmetry_conserving_bravyi_kitaev.py index cc01ffa9d..56622c305 100644 --- a/qsdk/toolboxes/qubit_mappings/symmetry_conserving_bravyi_kitaev.py +++ b/tangelo/toolboxes/qubit_mappings/symmetry_conserving_bravyi_kitaev.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tangelo/toolboxes/qubit_mappings/tests/__init__.py b/tangelo/toolboxes/qubit_mappings/tests/__init__.py new file mode 100644 index 000000000..81a799660 --- /dev/null +++ b/tangelo/toolboxes/qubit_mappings/tests/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/qsdk/toolboxes/qubit_mappings/tests/test_bravyi_kitaev.py b/tangelo/toolboxes/qubit_mappings/tests/test_bravyi_kitaev.py similarity index 80% rename from qsdk/toolboxes/qubit_mappings/tests/test_bravyi_kitaev.py rename to tangelo/toolboxes/qubit_mappings/tests/test_bravyi_kitaev.py index 557ff83b9..900b58dfb 100644 --- a/qsdk/toolboxes/qubit_mappings/tests/test_bravyi_kitaev.py +++ b/tangelo/toolboxes/qubit_mappings/tests/test_bravyi_kitaev.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,8 +15,8 @@ import unittest from openfermion.transforms import bravyi_kitaev as openfermion_bravyi_kitaev -from qsdk.toolboxes.qubit_mappings.bravyi_kitaev import bravyi_kitaev -from qsdk.toolboxes.operators import FermionOperator, QubitOperator +from tangelo.toolboxes.qubit_mappings.bravyi_kitaev import bravyi_kitaev +from tangelo.toolboxes.operators import FermionOperator, QubitOperator class BravyiKitaevTest(unittest.TestCase): @@ -46,15 +46,15 @@ def test_openfermion_equivalence(self): input_operator += FermionOperator((13, 1), 0.2) n_qubits = 14 - qsdk_result = bravyi_kitaev(input_operator, n_qubits=n_qubits) + tangelo_result = bravyi_kitaev(input_operator, n_qubits=n_qubits) openfermion_result = openfermion_bravyi_kitaev(input_operator, n_qubits=n_qubits) #check that the number of terms is the same. - self.assertEqual(len(qsdk_result.terms), len(openfermion_result.terms), msg="Number of terms generated does not agree with openfermion implementation of Bravyi Kitaev.") + self.assertEqual(len(tangelo_result.terms), len(openfermion_result.terms), msg="Number of terms generated does not agree with openfermion implementation of Bravyi Kitaev.") #check that the term coefficients are the same - for ti in qsdk_result.terms: - factor = qsdk_result.terms[ti] + for ti in tangelo_result.terms: + factor = tangelo_result.terms[ti] openfermion_factor = openfermion_result.terms[ti] self.assertEqual(factor, openfermion_factor, msg="Term coefficient does not agree with openfermion bravyi_kitaev.") diff --git a/qsdk/toolboxes/qubit_mappings/tests/test_mapping_transform.py b/tangelo/toolboxes/qubit_mappings/tests/test_mapping_transform.py similarity index 96% rename from qsdk/toolboxes/qubit_mappings/tests/test_mapping_transform.py rename to tangelo/toolboxes/qubit_mappings/tests/test_mapping_transform.py index 5953bc74b..964489acf 100644 --- a/qsdk/toolboxes/qubit_mappings/tests/test_mapping_transform.py +++ b/tangelo/toolboxes/qubit_mappings/tests/test_mapping_transform.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -22,9 +22,9 @@ from openfermion.linalg import eigenspectrum from openfermion.linalg.sparse_tools import qubit_operator_sparse -from qsdk.toolboxes.operators import QubitOperator, FermionOperator -from qsdk.toolboxes.qubit_mappings import bravyi_kitaev, jordan_wigner -from qsdk.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping, make_up_then_down +from tangelo.toolboxes.operators import QubitOperator, FermionOperator +from tangelo.toolboxes.qubit_mappings import bravyi_kitaev, jordan_wigner +from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping, make_up_then_down class MappingTest(unittest.TestCase): diff --git a/qsdk/toolboxes/qubit_mappings/tests/test_qubitizer.py b/tangelo/toolboxes/qubit_mappings/tests/test_qubitizer.py similarity index 94% rename from qsdk/toolboxes/qubit_mappings/tests/test_qubitizer.py rename to tangelo/toolboxes/qubit_mappings/tests/test_qubitizer.py index 0fbd2ff51..88569a8e6 100644 --- a/qsdk/toolboxes/qubit_mappings/tests/test_qubitizer.py +++ b/tangelo/toolboxes/qubit_mappings/tests/test_qubitizer.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,8 +14,8 @@ import unittest -from qsdk.molecule_library import mol_H2_sto3g -from qsdk.toolboxes.qubit_mappings import jordan_wigner +from tangelo.molecule_library import mol_H2_sto3g +from tangelo.toolboxes.qubit_mappings import jordan_wigner def assert_term_dict_almost_equal(d1, d2, delta=1e-10): diff --git a/qsdk/toolboxes/qubit_mappings/tests/test_statevector_mapping.py b/tangelo/toolboxes/qubit_mappings/tests/test_statevector_mapping.py similarity index 94% rename from qsdk/toolboxes/qubit_mappings/tests/test_statevector_mapping.py rename to tangelo/toolboxes/qubit_mappings/tests/test_statevector_mapping.py index 8bec3370f..b9351c8aa 100644 --- a/qsdk/toolboxes/qubit_mappings/tests/test_statevector_mapping.py +++ b/tangelo/toolboxes/qubit_mappings/tests/test_statevector_mapping.py @@ -1,4 +1,4 @@ -# Copyright 2021 1QB Information Technologies Inc. +# Copyright 2021 Good Chemistry Company. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import unittest import numpy as np -from qsdk.toolboxes.qubit_mappings.statevector_mapping import get_vector, vector_to_circuit +from tangelo.toolboxes.qubit_mappings.statevector_mapping import get_vector, vector_to_circuit class TestVector(unittest.TestCase): From 9948de9c65874005c69097d6b521bf80e52b37f9 Mon Sep 17 00:00:00 2001 From: JamesB-1qbit <84878946+JamesB-1qbit@users.noreply.github.com> Date: Wed, 17 Nov 2021 15:21:16 -0500 Subject: [PATCH 15/68] upgraded setup to use pyscf-2.0.1 (#90) --- setup.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 741f3d54d..8ab5666ee 100755 --- a/setup.py +++ b/setup.py @@ -11,8 +11,8 @@ def install(package): long_description = f.read() install('wheel') -install('h5py==3.2.0') -install('pyscf==1.7.6') +install('pyscf') +install('git+https://github.com/pyscf/semiempirical') setuptools.setup( name="tangelo", @@ -25,6 +25,6 @@ def install(package): url="https://github.com/quantumsimulation/QEMIST_Tangelo", packages=setuptools.find_packages(), test_suite="tangelo", - setup_requires=['h5py==3.2.0'], - install_requires=['h5py==3.2.0', 'bitarray', 'openfermion', 'openfermionpyscf'] + setup_requires=['h5py'], + install_requires=['h5py', 'bitarray', 'openfermion', 'openfermionpyscf'] ) From 47e1c22fb75a878dfbe51a665e9b0b5bd110c2fa Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Fri, 19 Nov 2021 13:31:27 -0800 Subject: [PATCH 16/68] Create CODEOWNERS --- .github/CODEOWNERS | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..34adec120 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,17 @@ +# Default reviewers +* @ValentinS4t1qbit @AlexandreF-1qbit @JamesB-1qbit + +# DevOPs, scripts +*.sh @ValentinS4t1qbit +/.github/ @ValentinS4t1qbit + +# Docs +/docs/ @ValentinS4t1qbit @AlexandreF-1qbit +*.txt @ValentinS4t1qbit +*.rst @ValentinS4t1qbit + +# Tests +Tests/ @ValentinS4t1qbit + +# Backend-agnostic code +backendbuddy/ @ValentinS4t1qbit @JamesB-1qbit From ae0b8e308b9161e0d00c5d7b9de3c93bb82d7258 Mon Sep 17 00:00:00 2001 From: JamesB-1qbit <84878946+JamesB-1qbit@users.noreply.github.com> Date: Tue, 23 Nov 2021 15:19:34 -0500 Subject: [PATCH 17/68] fix scbk (#92) * fix scbk, take spin into account --- .../variational/adapt_vqe_solver.py | 7 +- tangelo/algorithms/variational/vqe_solver.py | 21 ++-- .../qubit_mappings/mapping_transform.py | 6 +- .../qubit_mappings/statevector_mapping.py | 15 ++- .../symmetry_conserving_bravyi_kitaev.py | 33 +++--- .../tests/test_statevector_mapping.py | 101 ++++++++++++++++++ 6 files changed, 143 insertions(+), 40 deletions(-) diff --git a/tangelo/algorithms/variational/adapt_vqe_solver.py b/tangelo/algorithms/variational/adapt_vqe_solver.py index 9910435f6..14556db25 100644 --- a/tangelo/algorithms/variational/adapt_vqe_solver.py +++ b/tangelo/algorithms/variational/adapt_vqe_solver.py @@ -136,13 +136,15 @@ def build(self): self.n_spinorbitals = self.molecule.n_active_sos self.n_electrons = self.molecule.n_active_electrons + self.spin = self.molecule.spin # Compute qubit hamiltonian for the input molecular system qubit_op = fermion_to_qubit_mapping(fermion_operator=self.molecule.fermionic_hamiltonian, mapping=self.qubit_mapping, n_spinorbitals=self.n_spinorbitals, n_electrons=self.n_electrons, - up_then_down=self.up_then_down) + up_then_down=self.up_then_down, + spin=self.spin) self.qubit_hamiltonian = qubitop_to_qubitham(qubit_op, self.qubit_mapping, self.up_then_down) @@ -183,7 +185,8 @@ def build(self): mapping=self.qubit_mapping, n_spinorbitals=self.n_spinorbitals, n_electrons=self.n_electrons, - up_then_down=self.up_then_down) for fi in self.fermionic_operators] + up_then_down=self.up_then_down, + spin=self.spin) for fi in self.fermionic_operators] else: raise ValueError('pool function must return either QubitOperator or FermionOperator') diff --git a/tangelo/algorithms/variational/vqe_solver.py b/tangelo/algorithms/variational/vqe_solver.py index 49e55d978..a8557bebc 100644 --- a/tangelo/algorithms/variational/vqe_solver.py +++ b/tangelo/algorithms/variational/vqe_solver.py @@ -131,7 +131,8 @@ def build(self): mapping=self.qubit_mapping, n_spinorbitals=self.molecule.n_active_sos, n_electrons=self.molecule.n_active_electrons, - up_then_down=self.up_then_down) + up_then_down=self.up_then_down, + spin=self.molecule.spin) self.qubit_hamiltonian = qubitop_to_qubitham(qubit_op, self.qubit_mapping, self.up_then_down) @@ -141,7 +142,8 @@ def build(self): mapping=self.qubit_mapping, n_spinorbitals=self.molecule.n_active_sos, n_electrons=self.molecule.n_active_electrons, - up_then_down=self.up_then_down) + up_then_down=self.up_then_down, + spin=self.molecule.spin) pen_qubit = qubitop_to_qubitham(pen_qubit, self.qubit_hamiltonian.mapping, self.qubit_hamiltonian.up_then_down) self.qubit_hamiltonian += pen_qubit @@ -240,7 +242,7 @@ def energy_estimation(self, var_params): return energy - def operator_expectation(self, operator, var_params=None, n_active_mos=None, n_active_electrons=None, n_active_sos=None): + def operator_expectation(self, operator, var_params=None, n_active_mos=None, n_active_electrons=None, n_active_sos=None, spin=None): """Obtains the operator expectation value of a given operator. Args: @@ -262,6 +264,7 @@ def operator_expectation(self, operator, var_params=None, n_active_mos=None, n_a required when operator is of type FermionOperator and mapping used is scbk and vqe_solver was initiated using a QubitHamiltonian. + spin (int): Spin (n_alpha - n_beta) Returns: float: operator expectation value computed by VQE using the @@ -295,17 +298,20 @@ def operator_expectation(self, operator, var_params=None, n_active_mos=None, n_a raise TypeError("operator must be a of string, FermionOperator or QubitOperator type.") if isinstance(operator, (str, FermionOperator)): - if (n_active_electrons is None or n_active_sos is None) and self.qubit_hamiltonian.mapping == "scbk": + if (n_active_electrons is None or n_active_sos is None or spin is None) and self.qubit_hamiltonian.mapping == "scbk": if self.molecule: n_active_electrons = self.molecule.n_active_electrons n_active_sos = self.molecule.n_active_sos + spin = self.molecule.spin else: - raise KeyError("Must supply n_active_electrons and n_active_sos with a FermionOperator and scbk mapping.") + raise KeyError("Must supply n_active_electrons, n_active_sos, and spin with a FermionOperator and scbk mapping.") + self.qubit_hamiltonian = fermion_to_qubit_mapping(fermion_operator=exp_op, mapping=self.qubit_hamiltonian.mapping, n_spinorbitals=n_active_sos, n_electrons=n_active_electrons, - up_then_down=self.qubit_hamiltonian.up_then_down) + up_then_down=self.qubit_hamiltonian.up_then_down, + spin=spin) expectation = self.energy_estimation(var_params) @@ -379,7 +385,8 @@ def get_rdm(self, var_params, resample=False, sum_spin=True): mapping=self.qubit_mapping, n_spinorbitals=self.molecule.n_active_sos, n_electrons=self.molecule.n_active_electrons, - up_then_down=self.up_then_down) + up_then_down=self.up_then_down, + spin=self.molecule.spin) qubit_hamiltonian2.compress() # Run through each qubit term separately, use previously calculated result for the qubit term or diff --git a/tangelo/toolboxes/qubit_mappings/mapping_transform.py b/tangelo/toolboxes/qubit_mappings/mapping_transform.py index cbc5c8a5d..4fe59bf59 100644 --- a/tangelo/toolboxes/qubit_mappings/mapping_transform.py +++ b/tangelo/toolboxes/qubit_mappings/mapping_transform.py @@ -70,7 +70,7 @@ def get_fermion_operator(operator): return fermion_operator -def fermion_to_qubit_mapping(fermion_operator, mapping, n_spinorbitals=None, n_electrons=None, up_then_down=False): +def fermion_to_qubit_mapping(fermion_operator, mapping, n_spinorbitals=None, n_electrons=None, up_then_down=False, spin=0): """Perform mapping of fermionic operator to qubit operator. This function is mostly a wrapper around standard openfermion code, with some important distinctions. We strictly enforce the specification of n_qubits for @@ -90,6 +90,7 @@ def fermion_to_qubit_mapping(fermion_operator, mapping, n_spinorbitals=None, n_e Required for symmetry conserving Bravyi-Kitaev only. up_then_down (bool): flag to change basis ordering, putting all spin up then all spin down. + spin (int): The spin number (n_alpha - n_beta) Returns: QubitOperator: input operator, encoded in the qubit space. @@ -121,7 +122,8 @@ def fermion_to_qubit_mapping(fermion_operator, mapping, n_spinorbitals=None, n_e qubit_operator = symmetry_conserving_bravyi_kitaev(fermion_operator=fermion_operator, n_spinorbitals=n_spinorbitals, n_electrons=n_electrons, - up_then_down=up_then_down) + up_then_down=up_then_down, + spin=spin) converted_qubit_op = QubitOperator() converted_qubit_op.terms = qubit_operator.terms.copy() diff --git a/tangelo/toolboxes/qubit_mappings/statevector_mapping.py b/tangelo/toolboxes/qubit_mappings/statevector_mapping.py index 65919d0a9..6b8b6d946 100644 --- a/tangelo/toolboxes/qubit_mappings/statevector_mapping.py +++ b/tangelo/toolboxes/qubit_mappings/statevector_mapping.py @@ -68,7 +68,8 @@ def get_vector(n_spinorbitals, n_electrons, mapping, up_then_down=False, spin=No elif mapping.upper() == "SCBK": if not up_then_down: warnings.warn("Symmetry-conserving Bravyi-Kitaev enforces all spin-up followed by all spin-down ordering.", RuntimeWarning) - return do_scbk_transform(n_spinorbitals, n_electrons) + vector = np.concatenate((vector[::2], vector[1::2])) + return do_scbk_transform(vector, n_spinorbitals) def do_bk_transform(vector): @@ -86,22 +87,20 @@ def do_bk_transform(vector): return vector_bk -def do_scbk_transform(n_spinorbitals, n_electrons): +def do_scbk_transform(vector, n_spinorbitals): """Instantiate qubit vector for symmetry-conserving Bravyi-Kitaev transformation. Based on implementation by Yukio Kawashima in DMET project. Args: + vector (numpy array of int): fermion occupation vector. n_spinorbitals (int): number of qubits in register. - n_electrons (int): number of fermions occupied. Returns: numpy array of int: qubit-encoded occupation vector. """ - n_alpha, n_orb = n_electrons//2, (n_spinorbitals - 2)//2 - vector = np.zeros(n_spinorbitals - 2, dtype=int) - if n_alpha >= 1: - vector[:n_alpha - 1] = 1 - vector[n_orb:n_orb + n_alpha - 1] = 1 + vector_bk = do_bk_transform(vector) + vector = np.delete(vector_bk, n_spinorbitals - 1) + vector = np.delete(vector, n_spinorbitals//2 - 1) return vector diff --git a/tangelo/toolboxes/qubit_mappings/symmetry_conserving_bravyi_kitaev.py b/tangelo/toolboxes/qubit_mappings/symmetry_conserving_bravyi_kitaev.py index 56622c305..0f50dc23f 100644 --- a/tangelo/toolboxes/qubit_mappings/symmetry_conserving_bravyi_kitaev.py +++ b/tangelo/toolboxes/qubit_mappings/symmetry_conserving_bravyi_kitaev.py @@ -40,7 +40,7 @@ def symmetry_conserving_bravyi_kitaev(fermion_operator, n_spinorbitals, - n_electrons, up_then_down=False): + n_electrons, up_then_down=False, spin=0): """Returns the QubitOperator for the FermionOperator supplied, with two qubits removed using conservation of (parity) of electron spin and number, as described in arXiv:1701.08213. This function has been modified from its @@ -87,7 +87,7 @@ def symmetry_conserving_bravyi_kitaev(fermion_operator, n_spinorbitals, raise ValueError("Number of electrons should be an integer.") if n_spinorbitals < count_qubits(fermion_operator): raise ValueError("Number of spin-orbitals is too small for FermionOperator input.") - #Check that the input operator is suitable for application of scBK + # Check that the input operator is suitable for application of scBK check_operator(fermion_operator, num_orbitals=(n_spinorbitals//2), up_then_down=up_then_down) # If necessary, arrange spins up then down, then BK map to qubit Hamiltonian. @@ -96,30 +96,21 @@ def symmetry_conserving_bravyi_kitaev(fermion_operator, n_spinorbitals, qubit_operator = bravyi_kitaev_tree(fermion_operator, n_qubits=n_spinorbitals) qubit_operator.compress() - # Allocates the parity factors for the orbitals as in arXiv:1704.05018. - remainder = n_electrons % 4 - if remainder == 0: - parity_final_orb = 1 - parity_middle_orb = 1 - elif remainder == 1: - parity_final_orb = -1 - parity_middle_orb = -1 - elif remainder == 2: - parity_final_orb = 1 - parity_middle_orb = -1 - else: - parity_final_orb = -1 - parity_middle_orb = 1 + n_alpha = n_electrons//2 + spin//2 + (n_electrons % 2) + + # Allocates the parity factors for the orbitals as in arXiv:1704.08213. + parity_final_orb = (-1)**n_electrons + parity_middle_orb = (-1)**n_alpha # Removes the final qubit, then the middle qubit. qubit_operator = edit_operator_for_spin(qubit_operator, - n_spinorbitals, - parity_final_orb) + n_spinorbitals, + parity_final_orb) qubit_operator = edit_operator_for_spin(qubit_operator, - n_spinorbitals/2, - parity_middle_orb) + n_spinorbitals/2, + parity_middle_orb) - #We remove the N/2-th and N-th qubit from the register. + # We remove the N/2-th and N-th qubit from the register. to_prune = (n_spinorbitals//2 - 1, n_spinorbitals - 1) qubit_operator = prune_unused_indices(qubit_operator, prune_indices=to_prune, n_qubits=n_spinorbitals) diff --git a/tangelo/toolboxes/qubit_mappings/tests/test_statevector_mapping.py b/tangelo/toolboxes/qubit_mappings/tests/test_statevector_mapping.py index b9351c8aa..e708a0ce1 100644 --- a/tangelo/toolboxes/qubit_mappings/tests/test_statevector_mapping.py +++ b/tangelo/toolboxes/qubit_mappings/tests/test_statevector_mapping.py @@ -20,6 +20,11 @@ import numpy as np from tangelo.toolboxes.qubit_mappings.statevector_mapping import get_vector, vector_to_circuit +from tangelo.molecule_library import mol_H4_sto3g, mol_H4_cation_sto3g +from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping +from tangelo.backendbuddy import Simulator + +sim = Simulator() class TestVector(unittest.TestCase): @@ -65,6 +70,102 @@ def test_circuit_build(self): self.assertEqual(circuit.size, sum(vector)) self.assertEqual(circuit.width, vector.size) + def test_all_same_energy_mol_H4_sto3g(self): + """Check that all mappings return statevectors that have the same energy expectation + for an even number of electrons and various spins""" + ferm_op = mol_H4_sto3g.fermionic_hamiltonian + qu_op_bk = fermion_to_qubit_mapping(ferm_op, + "BK", + mol_H4_sto3g.n_active_sos, + mol_H4_sto3g.n_active_electrons, + up_then_down=True) + qu_op_jw = fermion_to_qubit_mapping(ferm_op, + "JW", + mol_H4_sto3g.n_active_sos, + mol_H4_sto3g.n_active_electrons, + up_then_down=True) + # Test for spin 0, 2, and 4 + for spin in range(3): + vector_bk = get_vector(mol_H4_sto3g.n_active_sos, + mol_H4_sto3g.n_active_electrons, + mapping="BK", + up_then_down=True, + spin=spin*2) + vector_scbk = get_vector(mol_H4_sto3g.n_active_sos, + mol_H4_sto3g.n_active_electrons, + mapping="SCBK", + up_then_down=True, + spin=spin*2) + vector_jw = get_vector(mol_H4_sto3g.n_active_sos, + mol_H4_sto3g.n_active_electrons, + mapping="JW", + up_then_down=True, + spin=spin*2) + circuit_bk = vector_to_circuit(vector_bk) + circuit_scbk = vector_to_circuit(vector_scbk) + circuit_jw = vector_to_circuit(vector_jw) + + qu_op_scbk = fermion_to_qubit_mapping(ferm_op, + 'SCBK', + mol_H4_sto3g.n_active_sos, + mol_H4_sto3g.n_active_electrons, + up_then_down=True, + spin=spin*2) + + e_bk = sim.get_expectation_value(qu_op_bk, circuit_bk) + e_scbk = sim.get_expectation_value(qu_op_scbk, circuit_scbk) + e_jw = sim.get_expectation_value(qu_op_jw, circuit_jw) + self.assertAlmostEqual(e_bk, e_jw, places=7, msg=f"Failed for bk vs jw for spin={spin}") + self.assertAlmostEqual(e_jw, e_scbk, places=7, msg=f"Failed for jw vs scbk for spin={spin}") + + def test_all_same_energy_mol_H4_cation_sto3g(self): + """Check that all mappings return statevectors that have the same energy expectation + for an odd number of electrons and various spins""" + ferm_op = mol_H4_cation_sto3g.fermionic_hamiltonian + qu_op_bk = fermion_to_qubit_mapping(ferm_op, + "BK", + mol_H4_cation_sto3g.n_active_sos, + mol_H4_cation_sto3g.n_active_electrons, + up_then_down=True) + qu_op_jw = fermion_to_qubit_mapping(ferm_op, + "JW", + mol_H4_cation_sto3g.n_active_sos, + mol_H4_cation_sto3g.n_active_electrons, + up_then_down=True) + # Test for spin 1 and 3 + for spin in range(2): + vector_bk = get_vector(mol_H4_cation_sto3g.n_active_sos, + mol_H4_cation_sto3g.n_active_electrons, + mapping="BK", + up_then_down=True, + spin=spin*2+1) + vector_scbk = get_vector(mol_H4_cation_sto3g.n_active_sos, + mol_H4_cation_sto3g.n_active_electrons, + mapping="SCBK", + up_then_down=True, + spin=spin*2+1) + vector_jw = get_vector(mol_H4_cation_sto3g.n_active_sos, + mol_H4_cation_sto3g.n_active_electrons, + mapping="JW", + up_then_down=True, + spin=spin*2+1) + circuit_bk = vector_to_circuit(vector_bk) + circuit_scbk = vector_to_circuit(vector_scbk) + circuit_jw = vector_to_circuit(vector_jw) + + qu_op_scbk = fermion_to_qubit_mapping(ferm_op, + 'SCBK', + mol_H4_cation_sto3g.n_active_sos, + mol_H4_cation_sto3g.n_active_electrons, + up_then_down=True, + spin=spin*2+1) + + e_bk = sim.get_expectation_value(qu_op_bk, circuit_bk) + e_scbk = sim.get_expectation_value(qu_op_scbk, circuit_scbk) + e_jw = sim.get_expectation_value(qu_op_jw, circuit_jw) + self.assertAlmostEqual(e_bk, e_jw, places=7, msg=f"Failed for bk vs jw for spin={spin}") + self.assertAlmostEqual(e_jw, e_scbk, places=7, msg=f"Failed for jw vs scbk for spin={spin}") + if __name__ == "__main__": unittest.main() From 66660bf41dd8a10c42324b58ae4ad74577874277 Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Fri, 3 Dec 2021 09:47:08 -0800 Subject: [PATCH 18/68] Name change: backendbuddy -> linq (#93) * backendbuddy -> link in repo source code, name changes (file, folders) and symlinks --- .github/CODEOWNERS | 2 +- docs/source/backendbuddy_basics.ipynb | 1 - docs/source/linq_basics.ipynb | 1 + docs/source/tutorials.rst | 2 +- .../{backendbuddy => linq}/1.the_basics.ipynb | 24 +++++++++--------- .../3.noisy_simulation.ipynb | 10 ++++---- examples/{backendbuddy => linq}/VQE_H2.py | 2 +- .../{backendbuddy => linq}/custom_ansatz.py | 2 +- .../img/honeywell_dashboard.png | Bin .../qsharp/qsharp_circuit_generation.py | 6 ++--- .../qsharp/qsharp_execution.py | 4 +-- examples/overview_endtoend.ipynb | 18 ++++++------- ...st_cloud_hardware_experiments_braket.ipynb | 4 +-- examples/test_notebooks.py | 8 +++--- examples/vqe.ipynb | 16 ++++++------ examples/vqe_custom_ansatz_hamiltonian.ipynb | 4 +-- .../variational/adapt_vqe_solver.py | 2 +- .../variational/tests/test_vqe_solver.py | 2 +- tangelo/algorithms/variational/vqe_solver.py | 6 ++--- tangelo/helpers/__init__.py | 2 +- tangelo/{backendbuddy => linq}/__init__.py | 0 tangelo/{backendbuddy => linq}/circuit.py | 2 +- tangelo/{backendbuddy => linq}/gate.py | 0 .../helpers/__init__.py | 0 .../helpers/circuits/__init__.py | 0 .../helpers/circuits/measurement_basis.py | 2 +- .../helpers/operators/__init__.py | 0 .../helpers/operators/operators.py | 0 .../noisy_simulation/__init__.py | 0 .../noisy_simulation/noise_models.py | 2 +- .../{backendbuddy => linq}/qdk_template.py | 0 .../qpu_connection/__init__.py | 0 .../qpu_connection/qemist_cloud_connection.py | 0 tangelo/{backendbuddy => linq}/simulator.py | 8 +++--- .../{backendbuddy => linq}/tests/__init__.py | 0 .../tests/data/H2_UCCSD.qasm | 0 .../tests/data/H2_qubit_hamiltonian.txt | 0 .../tests/data/H4_UCCSD.qasm | 0 .../tests/data/H4_qubit_hamiltonian.txt | 0 .../tests/data/projectq_circuit.txt | 0 .../tests/test_circuits.py | 2 +- .../tests/test_gates.py | 2 +- .../tests/test_simulator.py | 4 +-- .../tests/test_simulator_noisy.py | 4 +-- .../tests/test_translator.py | 4 +-- .../translator/__init__.py | 0 .../translator/qdk_template.py | 0 .../translator/translate_braket.py | 0 .../translator/translate_cirq.py | 0 .../translator/translate_json_ionq.py | 0 .../translator/translate_openqasm.py | 2 +- .../translator/translate_projectq.py | 2 +- .../translator/translate_qdk.py | 0 .../translator/translate_qiskit.py | 0 .../translator/translate_qulacs.py | 0 .../ansatz_generator/_hea_circuit.py | 2 +- .../ansatz_generator/adapt_ansatz.py | 2 +- .../ansatz_generator/ansatz_utils.py | 2 +- tangelo/toolboxes/ansatz_generator/hea.py | 2 +- tangelo/toolboxes/ansatz_generator/rucc.py | 2 +- .../tests/test_fermionic_operators.py | 2 +- .../ansatz_generator/tests/test_hea.py | 2 +- .../tests/test_penalty_terms.py | 2 +- .../ansatz_generator/tests/test_uccsd.py | 2 +- .../ansatz_generator/tests/test_upccgsd.py | 2 +- .../tests/test_variational_circuit.py | 2 +- tangelo/toolboxes/ansatz_generator/uccsd.py | 2 +- tangelo/toolboxes/ansatz_generator/upccgsd.py | 2 +- .../ansatz_generator/variational_circuit.py | 2 +- .../measurements/qubit_terms_grouping.py | 2 +- tangelo/toolboxes/measurements/tests/data | 2 +- .../measurements/tests/test_measurements.py | 4 +-- .../tests/test_qubit_terms_grouping.py | 2 +- .../qubit_mappings/statevector_mapping.py | 6 ++--- .../tests/test_statevector_mapping.py | 2 +- 75 files changed, 98 insertions(+), 98 deletions(-) delete mode 120000 docs/source/backendbuddy_basics.ipynb create mode 120000 docs/source/linq_basics.ipynb rename examples/{backendbuddy => linq}/1.the_basics.ipynb (95%) rename examples/{backendbuddy => linq}/3.noisy_simulation.ipynb (98%) rename examples/{backendbuddy => linq}/VQE_H2.py (98%) rename examples/{backendbuddy => linq}/custom_ansatz.py (99%) rename examples/{backendbuddy => linq}/img/honeywell_dashboard.png (100%) rename examples/{backendbuddy => linq}/qsharp/qsharp_circuit_generation.py (87%) rename examples/{backendbuddy => linq}/qsharp/qsharp_execution.py (93%) rename tangelo/{backendbuddy => linq}/__init__.py (100%) rename tangelo/{backendbuddy => linq}/circuit.py (99%) rename tangelo/{backendbuddy => linq}/gate.py (100%) rename tangelo/{backendbuddy => linq}/helpers/__init__.py (100%) rename tangelo/{backendbuddy => linq}/helpers/circuits/__init__.py (100%) rename tangelo/{backendbuddy => linq}/helpers/circuits/measurement_basis.py (98%) rename tangelo/{backendbuddy => linq}/helpers/operators/__init__.py (100%) rename tangelo/{backendbuddy => linq}/helpers/operators/operators.py (100%) rename tangelo/{backendbuddy => linq}/noisy_simulation/__init__.py (100%) rename tangelo/{backendbuddy => linq}/noisy_simulation/noise_models.py (99%) rename tangelo/{backendbuddy => linq}/qdk_template.py (100%) rename tangelo/{backendbuddy => linq}/qpu_connection/__init__.py (100%) rename tangelo/{backendbuddy => linq}/qpu_connection/qemist_cloud_connection.py (100%) rename tangelo/{backendbuddy => linq}/simulator.py (98%) rename tangelo/{backendbuddy => linq}/tests/__init__.py (100%) rename tangelo/{backendbuddy => linq}/tests/data/H2_UCCSD.qasm (100%) rename tangelo/{backendbuddy => linq}/tests/data/H2_qubit_hamiltonian.txt (100%) rename tangelo/{backendbuddy => linq}/tests/data/H4_UCCSD.qasm (100%) rename tangelo/{backendbuddy => linq}/tests/data/H4_qubit_hamiltonian.txt (100%) rename tangelo/{backendbuddy => linq}/tests/data/projectq_circuit.txt (100%) rename tangelo/{backendbuddy => linq}/tests/test_circuits.py (98%) rename tangelo/{backendbuddy => linq}/tests/test_gates.py (98%) rename tangelo/{backendbuddy => linq}/tests/test_simulator.py (99%) rename tangelo/{backendbuddy => linq}/tests/test_simulator_noisy.py (98%) rename tangelo/{backendbuddy => linq}/tests/test_translator.py (99%) rename tangelo/{backendbuddy => linq}/translator/__init__.py (100%) rename tangelo/{backendbuddy => linq}/translator/qdk_template.py (100%) rename tangelo/{backendbuddy => linq}/translator/translate_braket.py (100%) rename tangelo/{backendbuddy => linq}/translator/translate_cirq.py (100%) rename tangelo/{backendbuddy => linq}/translator/translate_json_ionq.py (100%) rename tangelo/{backendbuddy => linq}/translator/translate_openqasm.py (99%) rename tangelo/{backendbuddy => linq}/translator/translate_projectq.py (99%) rename tangelo/{backendbuddy => linq}/translator/translate_qdk.py (100%) rename tangelo/{backendbuddy => linq}/translator/translate_qiskit.py (100%) rename tangelo/{backendbuddy => linq}/translator/translate_qulacs.py (100%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 34adec120..18e42d08f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -14,4 +14,4 @@ Tests/ @ValentinS4t1qbit # Backend-agnostic code -backendbuddy/ @ValentinS4t1qbit @JamesB-1qbit +linq/ @ValentinS4t1qbit @JamesB-1qbit diff --git a/docs/source/backendbuddy_basics.ipynb b/docs/source/backendbuddy_basics.ipynb deleted file mode 120000 index 7ee227138..000000000 --- a/docs/source/backendbuddy_basics.ipynb +++ /dev/null @@ -1 +0,0 @@ -../../examples/backendbuddy/1.the_basics.ipynb \ No newline at end of file diff --git a/docs/source/linq_basics.ipynb b/docs/source/linq_basics.ipynb new file mode 120000 index 000000000..47a2dbcf5 --- /dev/null +++ b/docs/source/linq_basics.ipynb @@ -0,0 +1 @@ +../../examples/linq/1.the_basics.ipynb \ No newline at end of file diff --git a/docs/source/tutorials.rst b/docs/source/tutorials.rst index e90011347..5bd854a55 100644 --- a/docs/source/tutorials.rst +++ b/docs/source/tutorials.rst @@ -5,7 +5,7 @@ Tutorials :maxdepth: 2 overview_endtoend - backendbuddy_basics + linq_basics vqe dmet problem_decomposition_oniom diff --git a/examples/backendbuddy/1.the_basics.ipynb b/examples/linq/1.the_basics.ipynb similarity index 95% rename from examples/backendbuddy/1.the_basics.ipynb rename to examples/linq/1.the_basics.ipynb index 1f828193e..7d1accddc 100755 --- a/examples/backendbuddy/1.the_basics.ipynb +++ b/examples/linq/1.the_basics.ipynb @@ -11,7 +11,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "`backendbuddy` is a submodule of `tangelo` that helps you connect to and leverage the features of various backends, may they be simulators or QPUs. This notebook talks about the abstract format used in this python package to represent a quantum circuit, and how we can then translate it to other formats or objects used in popular packages such as Qiskit, ProjectQ, Qulacs (...) and leverage the different features and performance they offer.\n", + "`linq` is a submodule of `tangelo` that helps you connect to and leverage the features of various backends, may they be simulators or QPUs. This notebook talks about the abstract format used in this python package to represent a quantum circuit, and how we can then translate it to other formats or objects used in popular packages such as Qiskit, ProjectQ, Qulacs (...) and leverage the different features and performance they offer.\n", "\n", "This functionality is pretty useful because it means that you can derive a given quantum circuit for any supported compute backend available with minimal effort, whether it is a simulator or an actual QPU. You therefore do not need to rewrite your program to run on a different platform, or to share your code in a specific format that your collaborators or clients expect, for publication or running an actual hardware experiment. It also means that whatever new method or code you develop can now suddenly run on all the compute backends available, enabling researchers and product users to benefit from your contributions regardless of the compute platform they intend to use.\n", "\n", @@ -47,7 +47,7 @@ "source": [ "## 1. Abstract gate class \n", "\n", - "The `backendbuddy` submodule is not here to provide yet another language to express a quantum circuit or to sell you an elaborate syntax to be used as a standard. Instead, it aims at providing users a straightforward and transparent way to represent a quantum gate operation, and by extension a quantum circuit.\n", + "The `linq` submodule is not here to provide yet another language to express a quantum circuit or to sell you an elaborate syntax to be used as a standard. Instead, it aims at providing users a straightforward and transparent way to represent a quantum gate operation, and by extension a quantum circuit.\n", "\n", "The idea is to decouple quantum circuit definition, optimization, as well as post-processing, from the actual compute backend and state preparation: users are no longer necessarily tied to a given platform for their research or experiments and can easily jump from one to another depending on their needs. Do you need a faster simulator, or to use specific noise models only supported by specific backends? No need to rewrite your whole code.\n", "\n", @@ -85,7 +85,7 @@ } ], "source": [ - "from tangelo.backendbuddy import Gate\n", + "from tangelo.linq import Gate\n", "\n", "# Create a Hadamard gate acting on qubit 2\n", "H_gate = Gate(\"H\", 2)\n", @@ -166,7 +166,7 @@ } ], "source": [ - "from tangelo.backendbuddy import Circuit\n", + "from tangelo.linq import Circuit\n", "\n", "# Here's a list of abstract gates\n", "mygates = [Gate(\"H\", 2), Gate(\"CNOT\", 1, control=0), Gate(\"CNOT\", target=2, control=1),\n", @@ -266,7 +266,7 @@ } ], "source": [ - "from tangelo.backendbuddy.helpers.circuits import measurement_basis_gates, pauli_string_to_of\n", + "from tangelo.linq.helpers.circuits import measurement_basis_gates, pauli_string_to_of\n", "\n", "def theta_sweep(theta, m_basis):\n", " \"\"\" A single-parameter circuit, with change of basis at the end if needed \"\"\"\n", @@ -276,7 +276,7 @@ " my_gates += measurement_basis_gates(pauli_string_to_of(m_basis))\n", " return Circuit(my_gates)\n", "\n", - "# It is easy with backendbuddy to move between string or Openfermion-style representations for Pauli words\n", + "# It is easy with linq to move between string or Openfermion-style representations for Pauli words\n", "for theta, m_basis in [(0.1, 'ZZ'), (0.2, 'ZZ'), (0.3, 'XY')]:\n", " c = theta_sweep(theta, m_basis)\n", " print(f\"{c}\\n\")" @@ -316,7 +316,7 @@ } ], "source": [ - "from tangelo.backendbuddy import SUPPORTED_GATES\n", + "from tangelo.linq import SUPPORTED_GATES\n", "\n", "for backend, gates in SUPPORTED_GATES.items():\n", " print(f'{backend} : {gates}')" @@ -427,7 +427,7 @@ } ], "source": [ - "from tangelo.backendbuddy.translator import translate_qulacs, translate_qiskit, translate_projectq, translate_qsharp, translate_openqasm, translate_cirq, translate_json_ionq, translate_braket\n", + "from tangelo.linq.translator import translate_qulacs, translate_qiskit, translate_projectq, translate_qsharp, translate_openqasm, translate_cirq, translate_json_ionq, translate_braket\n", "\n", "circ3_projectq = translate_projectq(circuit3)\n", "print(f\"{circ3_projectq}\\n\")\n", @@ -603,7 +603,7 @@ "\n", "### Saving, loading and sharing quantum circuits\n", "\n", - "The abstract circuit class used by the backendbuddy package can be translated into various formats and objects, so that users can easily save, load and share them with collaborators without providing an explicit code to do so. This is particularly relevant if you are working with external collaborators that need a circuit in a specific or general format, or if the circuit you want to share comes at the expense of complex calculations (using an application library, or maybe as a result of a variational or iterative procedure).\n", + "The abstract circuit class used by the linq package can be translated into various formats and objects, so that users can easily save, load and share them with collaborators without providing an explicit code to do so. This is particularly relevant if you are working with external collaborators that need a circuit in a specific or general format, or if the circuit you want to share comes at the expense of complex calculations (using an application library, or maybe as a result of a variational or iterative procedure).\n", "\n", "A first option is to use the function translating abstract format to OpenQASM, which allows you to export your circuit in a straightforward OpenQASM 2.0 text format (it relies on the QASM export from the IBM Qiskit package, as they drive the standard), which is both human readable and can be imported by other quantum circuit simulation packages in general. A \"reverse\" translation from OpenQASM back to abstract format is available as well, but only supports a subset of OPENQASM 2.0. Users can thus save as QASM and load this back into an abstract circuit as well. No need to rewrite anything.\n", "\n", @@ -640,7 +640,7 @@ } ], "source": [ - "from tangelo.backendbuddy import Simulator, backend_info\n", + "from tangelo.linq import Simulator, backend_info\n", "\n", "for backend, info in backend_info.items():\n", " print(f'{backend} : {info}')" @@ -762,7 +762,7 @@ "\n", "The abstract circuit format provides a `MEASURE` instruction, supported by most compute backends, with the intent of simulating mixed states / mid-circuit measurements in the computational basis (e.g along the Z axis). As mixed states cannot be represented by a statevector, the statevector simulator backends default to simulating the quantum circuit with shots, in order to return a histogram of frequencies. Users thus must ensure the `n_shots` attribute of their `Simulator` object has been set.\n", "\n", - "Simulating a mixed state can be considerably slower than simulating a pure state (some backends are particularly **not** good at this). Including final measurements in your state-preparation circuit is not recommended, if you intend to use `backendbuddy` to simulate it." + "Simulating a mixed state can be considerably slower than simulating a pure state (some backends are particularly **not** good at this). Including final measurements in your state-preparation circuit is not recommended, if you intend to use `linq` to simulate it." ] }, { @@ -848,7 +848,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This notebook provided a general introduction to the `backendbuddy` submodule. I hope you liked it.\n", + "This notebook provided a general introduction to the `linq` submodule. I hope you liked it.\n", "To dive into other topics, such as **noisy simulation** or how to use this package for **variational algorithms** for example, please refer to the other examples and tutorials available !\n", "\n", "---\n", diff --git a/examples/backendbuddy/3.noisy_simulation.ipynb b/examples/linq/3.noisy_simulation.ipynb similarity index 98% rename from examples/backendbuddy/3.noisy_simulation.ipynb rename to examples/linq/3.noisy_simulation.ipynb index be41c3df4..a8846646f 100755 --- a/examples/backendbuddy/3.noisy_simulation.ipynb +++ b/examples/linq/3.noisy_simulation.ipynb @@ -71,7 +71,7 @@ } ], "source": [ - "from tangelo.backendbuddy.noisy_simulation import NoiseModel, SUPPORTED_NOISE_MODELS\n", + "from tangelo.linq.noisy_simulation import NoiseModel, SUPPORTED_NOISE_MODELS\n", "\n", "print(f'Supported noise models: {SUPPORTED_NOISE_MODELS}\\n')\n", "\n", @@ -106,7 +106,7 @@ } ], "source": [ - "from tangelo.backendbuddy import ONE_QUBIT_GATES, TWO_QUBIT_GATES\n", + "from tangelo.linq import ONE_QUBIT_GATES, TWO_QUBIT_GATES\n", "\n", "nm = NoiseModel()\n", "\n", @@ -160,7 +160,7 @@ } ], "source": [ - "from tangelo.backendbuddy import backend_info\n", + "from tangelo.linq import backend_info\n", "from pprint import pprint \n", "\n", "pprint(backend_info)" @@ -189,7 +189,7 @@ } ], "source": [ - "from tangelo.backendbuddy import Simulator, Gate, Circuit\n", + "from tangelo.linq import Simulator, Gate, Circuit\n", "\n", "# Define a noise model\n", "nmp = NoiseModel()\n", @@ -367,7 +367,7 @@ } ], "source": [ - "from tangelo.backendbuddy.noisy_simulation import get_qiskit_noise_model\n", + "from tangelo.linq.noisy_simulation import get_qiskit_noise_model\n", "\n", "nm = NoiseModel()\n", "nm.add_quantum_error('RX', 'depol', 4/3)\n", diff --git a/examples/backendbuddy/VQE_H2.py b/examples/linq/VQE_H2.py similarity index 98% rename from examples/backendbuddy/VQE_H2.py rename to examples/linq/VQE_H2.py index 3e848bb23..9c9a23a12 100755 --- a/examples/backendbuddy/VQE_H2.py +++ b/examples/linq/VQE_H2.py @@ -9,7 +9,7 @@ import numpy as np from scipy.optimize import minimize -from tangelo.backendbuddy import Gate, Circuit, Simulator +from tangelo.linq import Gate, Circuit, Simulator class H2StatePreparationCircuit(Circuit): diff --git a/examples/backendbuddy/custom_ansatz.py b/examples/linq/custom_ansatz.py similarity index 99% rename from examples/backendbuddy/custom_ansatz.py rename to examples/linq/custom_ansatz.py index 5d65eb88e..9f244f5f1 100755 --- a/examples/backendbuddy/custom_ansatz.py +++ b/examples/linq/custom_ansatz.py @@ -21,7 +21,7 @@ change significantly or "grow" (such as ADAPT-VQE). """ -from tangelo.backendbuddy import Gate, Circuit +from tangelo.linq import Gate, Circuit class MyAnsatzCircuit(Circuit): diff --git a/examples/backendbuddy/img/honeywell_dashboard.png b/examples/linq/img/honeywell_dashboard.png similarity index 100% rename from examples/backendbuddy/img/honeywell_dashboard.png rename to examples/linq/img/honeywell_dashboard.png diff --git a/examples/backendbuddy/qsharp/qsharp_circuit_generation.py b/examples/linq/qsharp/qsharp_circuit_generation.py similarity index 87% rename from examples/backendbuddy/qsharp/qsharp_circuit_generation.py rename to examples/linq/qsharp/qsharp_circuit_generation.py index 2fc0aee6c..c6cfc6b9e 100755 --- a/examples/backendbuddy/qsharp/qsharp_circuit_generation.py +++ b/examples/linq/qsharp/qsharp_circuit_generation.py @@ -9,9 +9,9 @@ Q# code needs to be written to file, as it needs to be compiled before execution, regardless of the compute backend. """ -from tangelo.backendbuddy import Gate, Circuit -from tangelo.backendbuddy.translator import translate_qsharp -from tangelo.backendbuddy.helper_circuits import measurement_basis_gates, pauli_string_to_of +from tangelo.linq import Gate, Circuit +from tangelo.linq.translator import translate_qsharp +from tangelo.linq.helper_circuits import measurement_basis_gates, pauli_string_to_of def theta_sweep(theta, m_basis): diff --git a/examples/backendbuddy/qsharp/qsharp_execution.py b/examples/linq/qsharp/qsharp_execution.py similarity index 93% rename from examples/backendbuddy/qsharp/qsharp_execution.py rename to examples/linq/qsharp/qsharp_execution.py index 716da6a3d..372e90f0c 100755 --- a/examples/backendbuddy/qsharp/qsharp_execution.py +++ b/examples/linq/qsharp/qsharp_execution.py @@ -8,7 +8,7 @@ Qubits are numbered left-to-right in the results, in both cases (e.g q0q1q2...) -You can elegantly generate your Q# circuits using tangelo.backendbuddy and submit them right away in one single script. +You can elegantly generate your Q# circuits using tangelo.linq and submit them right away in one single script. """ @@ -56,5 +56,5 @@ # If your Q# operation takes no parameter (results need to be retrieved through Azure portal, under the relevant Quantum Workspace). job_id = qsharp.azure.submit(MyQsharpOperation, shots=n_shots, jobName=job_name) - # If your Q# operation takes parameters (currently not available in tangelo.backendbuddy, but you can write your own Q#): + # If your Q# operation takes parameters (currently not available in tangelo.linq, but you can write your own Q#): # job_id = qsharp.azure.submit(MyQsharpOperation, param1=value1, param2=value2, ..., shots=n_shots, jobName=job_name') diff --git a/examples/overview_endtoend.ipynb b/examples/overview_endtoend.ipynb index 2b9bd6cd5..5bfbf6ca1 100644 --- a/examples/overview_endtoend.ipynb +++ b/examples/overview_endtoend.ipynb @@ -353,7 +353,7 @@ "\n", "Quantum computers currently have limited access and capability. To study the applications of quantum computing on problem instances of reasonable size, we can use classical simulators and emulators in order to anticipate the behavior of quantum algorithms on real devices, in the presence or absence of noise. \n", "\n", - "This package provides a submodule called `backendbuddy`, which supports a collection of open-source quantum circuit simulators delivering different performance and features. We are free to choose the most relevant backend for our use cases, thinking about resource requirements, use of shots, presence or absence of noise, and accuracy of simulation, for example. Our algorithms manipulate circuits in our own intermediary representation, and a variety of functions exist in order to convert these objects into popular formats, compatible with other open-source tools.\n", + "This package provides a submodule called `linq`, which supports a collection of open-source quantum circuit simulators delivering different performance and features. We are free to choose the most relevant backend for our use cases, thinking about resource requirements, use of shots, presence or absence of noise, and accuracy of simulation, for example. Our algorithms manipulate circuits in our own intermediary representation, and a variety of functions exist in order to convert these objects into popular formats, compatible with other open-source tools.\n", "\n", "The `simulate` method below runs the DMET algorithm using a simulator backend. We could specify the desired backend in the variable `dmet_options` described above when creating the `DMETProblemDecomposition` object. We however did not, and the current default choice is to go for a noiseless simulator: since our package relies on [openfermion](https://quantumai.google/openfermion), which installs [cirq](https://quantumai.google/cirq) as a dependency, `cirq` will be the default backend unless [qulacs](https://github.com/qulacs/qulacs) is found in your environment. Currently other supported local backends include [qiskit](https://qiskit.org/) and [QDK](https://azure.microsoft.com/en-us/resources/development-kit/quantum-computing/).\n" ] @@ -602,8 +602,8 @@ } ], "source": [ - "from tangelo.backendbuddy import Circuit, Gate\n", - "from tangelo.backendbuddy.helpers.circuits.measurement_basis import measurement_basis_gates\n", + "from tangelo.linq import Circuit, Gate\n", + "from tangelo.linq.helpers.circuits.measurement_basis import measurement_basis_gates\n", "\n", "# Creation of XX, XZ, ZX, ZZ and YY circuits.\n", "# This is done by appending relevant gates to the quantum circuit representing the quantum state.\n", @@ -730,7 +730,7 @@ "os.environ['QEMIST_AUTH_TOKEN'] = \"your_qemist_authentication_token\"\n", "\n", "# Estimate, submit and get the results of your job / quantum task through our wrappers\n", - "from tangelo.backendbuddy.qpu_connection import job_submit, job_status, job_cancel, job_result, job_estimate\n", + "from tangelo.linq.qpu_connection import job_submit, job_status, job_cancel, job_result, job_estimate\n", "\n", "circuit_YY = quantum_circuit[((0, \"Y\"), (1, \"Y\"))]\n", "\n", @@ -751,7 +751,7 @@ "source": [ "### Using a cloud API and format conversion \n", "\n", - "The utility functions in `tangelo.backendbuddy` allow us to convert our generic `Circuit` objects into a variety of formats supported by other open-source packages and services, such as Amazon's Braket and Microsoft's Azure Quantum.\n", + "The utility functions in `tangelo.linq` allow us to convert our generic `Circuit` objects into a variety of formats supported by other open-source packages and services, such as Amazon's Braket and Microsoft's Azure Quantum.\n", "\n", "You can thus convert a `Circuit` object into the desired format and use the API of those services directly in order to reach a QPU or an online simulator if you wish to do so. The example below shows how to convert a `Circuit` object into the Braket format. Provided that you have a Braket account, the submission process is pretty straightforward, as demonstrated by the [documentation](https://github.com/aws/amazon-braket-sdk-python#usage)" ] @@ -778,7 +778,7 @@ } ], "source": [ - "from tangelo.backendbuddy.translator import translate_braket\n", + "from tangelo.linq.translator import translate_braket\n", "\n", "braket_circuit = translate_braket(circuit_YY)\n", "print(braket_circuit)" @@ -809,7 +809,7 @@ } ], "source": [ - "from tangelo.backendbuddy.translator import translate_cirq\n", + "from tangelo.linq.translator import translate_cirq\n", "\n", "cirq_circuit = translate_cirq(circuit_YY)\n", "print(cirq_circuit)" @@ -836,8 +836,8 @@ }, "outputs": [], "source": [ - "from tangelo.backendbuddy import Simulator\n", - "from tangelo.backendbuddy.noisy_simulation import NoiseModel\n", + "from tangelo.linq import Simulator\n", + "from tangelo.linq.noisy_simulation import NoiseModel\n", "\n", "nmp = NoiseModel()\n", "nmp.add_quantum_error(\"CNOT\", \"depol\", 0.01)\n", diff --git a/examples/qemist_cloud_hardware_experiments_braket.ipynb b/examples/qemist_cloud_hardware_experiments_braket.ipynb index a68d6f9bf..0e54fa2eb 100755 --- a/examples/qemist_cloud_hardware_experiments_braket.ipynb +++ b/examples/qemist_cloud_hardware_experiments_braket.ipynb @@ -91,7 +91,7 @@ } ], "source": [ - "from tangelo.backendbuddy import Gate, Circuit\n", + "from tangelo.linq import Gate, Circuit\n", "\n", "circuit = Circuit([Gate(\"H\", 0), Gate(\"CNOT\", 1, control=0)])\n", "print(circuit)" @@ -110,7 +110,7 @@ "metadata": {}, "outputs": [], "source": [ - "from tangelo.backendbuddy.qpu_connection import job_submit, job_status, job_cancel, job_result, job_estimate" + "from tangelo.linq.qpu_connection import job_submit, job_status, job_cancel, job_result, job_estimate" ] }, { diff --git a/examples/test_notebooks.py b/examples/test_notebooks.py index 703a76787..a5411b029 100755 --- a/examples/test_notebooks.py +++ b/examples/test_notebooks.py @@ -17,11 +17,11 @@ def run_notebook_as_test(notebook_path): class TestNotebooks(unittest.TestCase): """ Turn target Python notebooks into script, run them as unittests (pass = no errors at runtime) """ - def test_backendbuddy_basics_notebook(self): - run_notebook_as_test('backendbuddy/1.the_basics.ipynb') + def test_linq_basics_notebook(self): + run_notebook_as_test('linq/1.the_basics.ipynb') - def test_backendbuddy_noisy_simulation_notebook(self): - run_notebook_as_test('backendbuddy/3.noisy_simulation.ipynb') + def test_linq_noisy_simulation_notebook(self): + run_notebook_as_test('linq/3.noisy_simulation.ipynb') def test_dmet_notebook(self): run_notebook_as_test('./dmet.ipynb') diff --git a/examples/vqe.ipynb b/examples/vqe.ipynb index edb4884fa..20a42f22e 100755 --- a/examples/vqe.ipynb +++ b/examples/vqe.ipynb @@ -215,7 +215,7 @@ " ,\n", " ,\n", " },\n", - " 'backend': }" + " 'backend': }" ] }, "execution_count": 3, @@ -235,8 +235,8 @@ "In particular we notice:\n", "\n", "- **qubit Hamiltonian**: the qubit operator used for computing the expectation value in the energy estimation step of VQE\n", - "- **backend** is a `Simulator` object that was defined in a submodule called `backendbuddy`, allowing us to define how a quantum circuit should be run. Currently, the default option is to run using an exact simulation (no noise) using the `qulacs` classical simulator.\n", - "- **ansatz**: is now an object that implements the UCCSD ansatz. We go into more details about builtin and custom ansatze in a separate notebook. For now, it is useful to notice that one can peek at the UCCSD quantum circuit that was built, or the value of the variational parameters for this ansatz. This quantum circuit is in the format implemented in `backendbuddy`." + "- **backend** is a `Simulator` object that was defined in a submodule called `linq`, allowing us to define how a quantum circuit should be run. Currently, the default option is to run using an exact simulation (no noise) using the `qulacs` classical simulator.\n", + "- **ansatz**: is now an object that implements the UCCSD ansatz. We go into more details about builtin and custom ansatze in a separate notebook. For now, it is useful to notice that one can peek at the UCCSD quantum circuit that was built, or the value of the variational parameters for this ansatz. This quantum circuit is in the format implemented in `linq`." ] }, { @@ -423,7 +423,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "> :warning: If you are not familiar with the `backendbuddy` submodule, we recommend you check at some point the tutorial covering the basics. You can however keep going through this notebook without issues." + "> :warning: If you are not familiar with the `linq` submodule, we recommend you check at some point the tutorial covering the basics. You can however keep going through this notebook without issues." ] }, { @@ -515,7 +515,7 @@ "\n", "The UCCSD ansatz supports a few \"shortcut\" keywords for some parameters, such as \"MP2\", \"ones\" or \"random\" (don't hesitate to explore the `ansatz` attribute of your `VQESolver` object, in particular the `n_var_params` value). We can compare the energy they return: we see below that the mp2 parameters are a much better pick as initial parameters, but not as good as the optimal ones found by `simulate` earlier.\n", "\n", - "Since this method builds the ansatz circuit for the input parameter, you can then grab the corresponding circuit. If you know the optimal parameters (from maybe a previous call to `simulate`), you can then grab the optimal circuit. Check out the `backendbuddy` tutorials to get an idea of how you could explore, export and send this circuit to a collaborator or a compute backend !" + "Since this method builds the ansatz circuit for the input parameter, you can then grab the corresponding circuit. If you know the optimal parameters (from maybe a previous call to `simulate`), you can then grab the optimal circuit. Check out the `linq` tutorials to get an idea of how you could explore, export and send this circuit to a collaborator or a compute backend !" ] }, { @@ -887,9 +887,9 @@ "source": [ "## 6. Option: compute backend\n", "\n", - "The `VQESolver` class relies on the `backendbuddy` submodule in order to simulate your quantum circuits. \n", + "The `VQESolver` class relies on the `linq` submodule in order to simulate your quantum circuits. \n", "\n", - "For more information about the `backend` attribute of your `VQE_Solver` object, of type `tangelo.backendbuddy.Simulator`, we recommend you check the `backendbuddy` tutorial notebooks.\n", + "For more information about the `backend` attribute of your `VQE_Solver` object, of type `tangelo.linq.Simulator`, we recommend you check the `linq` tutorial notebooks.\n", "\n", "This object allows you to pick from a number of different classical simulators, with significantly different performance, some supporting noisy simulation. The parameters `n_shots` introduces statistical noise in the computation, which correlates to accuracy of energy estimation, while `noise_model` enables us to model noise in the circuit to an extent. These parameters are provided to enable the modeling of experiments that may be closer to the behaviour of quantum hardware.\n", "\n", @@ -997,7 +997,7 @@ "\n", "This concludes our overview of `VQESolver`. Due to the complexity of the topic, we did not explore in details some of the underlying objects it relies on, such as the ansatz object and the backend object.\n", "\n", - "For a more in-depth discussion on these objects, please refer to the documentation and check out the other notebooks we have in this folder, or maybe the `backendbuddy` notebooks." + "For a more in-depth discussion on these objects, please refer to the documentation and check out the other notebooks we have in this folder, or maybe the `linq` notebooks." ] } ], diff --git a/examples/vqe_custom_ansatz_hamiltonian.ipynb b/examples/vqe_custom_ansatz_hamiltonian.ipynb index b78f4a47b..b9679ab8b 100755 --- a/examples/vqe_custom_ansatz_hamiltonian.ipynb +++ b/examples/vqe_custom_ansatz_hamiltonian.ipynb @@ -22,7 +22,7 @@ "from tangelo.toolboxes.ansatz_generator.ansatz import Ansatz\n", "from tangelo.toolboxes.qubit_mappings.statevector_mapping import get_reference_circuit\n", "from tangelo.toolboxes.qubit_mappings.mapping_transform import get_qubit_number, fermion_to_qubit_mapping\n", - "from tangelo.backendbuddy import Circuit, Gate" + "from tangelo.linq import Circuit, Gate" ] }, { @@ -176,7 +176,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Next, we'll implement `update_var_params`, where the circuit is updated with a new batch of variational parameters. The `tangelo.backendbuddy` Circuit class keeps a record of the variational gates in the circuit, making this update very straightforward, and avoids having to rebuild the circuit from scratch. All variational gates in the circuit are updated as per the `var_params` argument." + "Next, we'll implement `update_var_params`, where the circuit is updated with a new batch of variational parameters. The `tangelo.linq` Circuit class keeps a record of the variational gates in the circuit, making this update very straightforward, and avoids having to rebuild the circuit from scratch. All variational gates in the circuit are updated as per the `var_params` argument." ] }, { diff --git a/tangelo/algorithms/variational/adapt_vqe_solver.py b/tangelo/algorithms/variational/adapt_vqe_solver.py index 14556db25..8a32a01f9 100644 --- a/tangelo/algorithms/variational/adapt_vqe_solver.py +++ b/tangelo/algorithms/variational/adapt_vqe_solver.py @@ -252,7 +252,7 @@ def rank_pool(self, pool_commutators, circuit, backend, tolerance=1e-3): generator. circuit (agnostic_simulator.Circuit): Circuit for measuring each commutator. - backend (tangelo.backendbuddy.Simulator): Backend to measure + backend (tangelo.linq.Simulator): Backend to measure expectation values. tolerance (float): Minimum value for gradient to be considered. diff --git a/tangelo/algorithms/variational/tests/test_vqe_solver.py b/tangelo/algorithms/variational/tests/test_vqe_solver.py index c9aadf561..861952d28 100644 --- a/tangelo/algorithms/variational/tests/test_vqe_solver.py +++ b/tangelo/algorithms/variational/tests/test_vqe_solver.py @@ -15,7 +15,7 @@ import unittest import numpy as np -from tangelo.backendbuddy import Simulator +from tangelo.linq import Simulator from tangelo.algorithms import BuiltInAnsatze, VQESolver from tangelo.molecule_library import mol_H2_sto3g, mol_H4_sto3g, mol_H4_cation_sto3g, mol_NaH_sto3g from tangelo.toolboxes.ansatz_generator.uccsd import UCCSD diff --git a/tangelo/algorithms/variational/vqe_solver.py b/tangelo/algorithms/variational/vqe_solver.py index a8557bebc..a1db930bc 100644 --- a/tangelo/algorithms/variational/vqe_solver.py +++ b/tangelo/algorithms/variational/vqe_solver.py @@ -24,8 +24,8 @@ from openfermion.ops.operators.qubit_operator import QubitOperator from tangelo.helpers.utils import HiddenPrints -from tangelo.backendbuddy import Simulator, Circuit -from tangelo.backendbuddy.helpers.circuits.measurement_basis import measurement_basis_gates +from tangelo.linq import Simulator, Circuit +from tangelo.linq.helpers.circuits.measurement_basis import measurement_basis_gates from tangelo.toolboxes.operators import count_qubits, FermionOperator, qubitop_to_qubitham from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping from tangelo.toolboxes.ansatz_generator.ansatz import Ansatz @@ -70,7 +70,7 @@ class VQESolver: and its behavior. initial_var_params (str or array-like) : initial value for the classical optimizer. - backend_options (dict) : parameters to build the tangelo.backendbuddy Simulator + backend_options (dict) : parameters to build the tangelo.linq Simulator class. penalty_terms (dict): parameters for penalty terms to append to target qubit Hamiltonian (see penaly_terms for more details). diff --git a/tangelo/helpers/__init__.py b/tangelo/helpers/__init__.py index 72b88bf48..ef608d13f 100644 --- a/tangelo/helpers/__init__.py +++ b/tangelo/helpers/__init__.py @@ -13,4 +13,4 @@ # limitations under the License. from .utils import * -from tangelo.backendbuddy.helpers import * +from tangelo.linq.helpers import * diff --git a/tangelo/backendbuddy/__init__.py b/tangelo/linq/__init__.py similarity index 100% rename from tangelo/backendbuddy/__init__.py rename to tangelo/linq/__init__.py diff --git a/tangelo/backendbuddy/circuit.py b/tangelo/linq/circuit.py similarity index 99% rename from tangelo/backendbuddy/circuit.py rename to tangelo/linq/circuit.py index ffcf680dc..7eda545f7 100644 --- a/tangelo/backendbuddy/circuit.py +++ b/tangelo/linq/circuit.py @@ -19,7 +19,7 @@ """ from typing import List -from tangelo.backendbuddy import Gate +from tangelo.linq import Gate class Circuit: diff --git a/tangelo/backendbuddy/gate.py b/tangelo/linq/gate.py similarity index 100% rename from tangelo/backendbuddy/gate.py rename to tangelo/linq/gate.py diff --git a/tangelo/backendbuddy/helpers/__init__.py b/tangelo/linq/helpers/__init__.py similarity index 100% rename from tangelo/backendbuddy/helpers/__init__.py rename to tangelo/linq/helpers/__init__.py diff --git a/tangelo/backendbuddy/helpers/circuits/__init__.py b/tangelo/linq/helpers/circuits/__init__.py similarity index 100% rename from tangelo/backendbuddy/helpers/circuits/__init__.py rename to tangelo/linq/helpers/circuits/__init__.py diff --git a/tangelo/backendbuddy/helpers/circuits/measurement_basis.py b/tangelo/linq/helpers/circuits/measurement_basis.py similarity index 98% rename from tangelo/backendbuddy/helpers/circuits/measurement_basis.py rename to tangelo/linq/helpers/circuits/measurement_basis.py index 693623165..f7b9531f4 100644 --- a/tangelo/backendbuddy/helpers/circuits/measurement_basis.py +++ b/tangelo/linq/helpers/circuits/measurement_basis.py @@ -15,7 +15,7 @@ """Helper function: pauli word rotations.""" import numpy as np -from tangelo.backendbuddy import Gate +from tangelo.linq import Gate def measurement_basis_gates(term): diff --git a/tangelo/backendbuddy/helpers/operators/__init__.py b/tangelo/linq/helpers/operators/__init__.py similarity index 100% rename from tangelo/backendbuddy/helpers/operators/__init__.py rename to tangelo/linq/helpers/operators/__init__.py diff --git a/tangelo/backendbuddy/helpers/operators/operators.py b/tangelo/linq/helpers/operators/operators.py similarity index 100% rename from tangelo/backendbuddy/helpers/operators/operators.py rename to tangelo/linq/helpers/operators/operators.py diff --git a/tangelo/backendbuddy/noisy_simulation/__init__.py b/tangelo/linq/noisy_simulation/__init__.py similarity index 100% rename from tangelo/backendbuddy/noisy_simulation/__init__.py rename to tangelo/linq/noisy_simulation/__init__.py diff --git a/tangelo/backendbuddy/noisy_simulation/noise_models.py b/tangelo/linq/noisy_simulation/noise_models.py similarity index 99% rename from tangelo/backendbuddy/noisy_simulation/noise_models.py rename to tangelo/linq/noisy_simulation/noise_models.py index 80ea95d28..51ac62846 100644 --- a/tangelo/backendbuddy/noisy_simulation/noise_models.py +++ b/tangelo/linq/noisy_simulation/noise_models.py @@ -20,7 +20,7 @@ """ -from tangelo.backendbuddy import ONE_QUBIT_GATES +from tangelo.linq import ONE_QUBIT_GATES SUPPORTED_NOISE_MODELS = {'depol', 'pauli'} diff --git a/tangelo/backendbuddy/qdk_template.py b/tangelo/linq/qdk_template.py similarity index 100% rename from tangelo/backendbuddy/qdk_template.py rename to tangelo/linq/qdk_template.py diff --git a/tangelo/backendbuddy/qpu_connection/__init__.py b/tangelo/linq/qpu_connection/__init__.py similarity index 100% rename from tangelo/backendbuddy/qpu_connection/__init__.py rename to tangelo/linq/qpu_connection/__init__.py diff --git a/tangelo/backendbuddy/qpu_connection/qemist_cloud_connection.py b/tangelo/linq/qpu_connection/qemist_cloud_connection.py similarity index 100% rename from tangelo/backendbuddy/qpu_connection/qemist_cloud_connection.py rename to tangelo/linq/qpu_connection/qemist_cloud_connection.py diff --git a/tangelo/backendbuddy/simulator.py b/tangelo/linq/simulator.py similarity index 98% rename from tangelo/backendbuddy/simulator.py rename to tangelo/linq/simulator.py index f59289bc6..7116010fe 100644 --- a/tangelo/backendbuddy/simulator.py +++ b/tangelo/linq/simulator.py @@ -39,9 +39,9 @@ from openfermion.ops import QubitOperator from tangelo.helpers.utils import default_simulator -from tangelo.backendbuddy import Gate, Circuit -from tangelo.backendbuddy.helpers.circuits.measurement_basis import measurement_basis_gates -import tangelo.backendbuddy.translator as translator +from tangelo.linq import Gate, Circuit +from tangelo.linq.helpers.circuits.measurement_basis import measurement_basis_gates +import tangelo.linq.translator as translator # Data-structure showing what functionalities are supported by the backend, in this package @@ -184,7 +184,7 @@ def simulate(self, source_circuit, return_statevector=False, initial_statevector # Drawing individual shots with the qasm simulator, for noisy simulation or simulating mixed states if self._noise_model or source_circuit.is_mixed_state: - from tangelo.backendbuddy.noisy_simulation.noise_models import get_qiskit_noise_model + from tangelo.linq.noisy_simulation.noise_models import get_qiskit_noise_model meas_range = range(source_circuit.width) translated_circuit.measure(meas_range, meas_range) diff --git a/tangelo/backendbuddy/tests/__init__.py b/tangelo/linq/tests/__init__.py similarity index 100% rename from tangelo/backendbuddy/tests/__init__.py rename to tangelo/linq/tests/__init__.py diff --git a/tangelo/backendbuddy/tests/data/H2_UCCSD.qasm b/tangelo/linq/tests/data/H2_UCCSD.qasm similarity index 100% rename from tangelo/backendbuddy/tests/data/H2_UCCSD.qasm rename to tangelo/linq/tests/data/H2_UCCSD.qasm diff --git a/tangelo/backendbuddy/tests/data/H2_qubit_hamiltonian.txt b/tangelo/linq/tests/data/H2_qubit_hamiltonian.txt similarity index 100% rename from tangelo/backendbuddy/tests/data/H2_qubit_hamiltonian.txt rename to tangelo/linq/tests/data/H2_qubit_hamiltonian.txt diff --git a/tangelo/backendbuddy/tests/data/H4_UCCSD.qasm b/tangelo/linq/tests/data/H4_UCCSD.qasm similarity index 100% rename from tangelo/backendbuddy/tests/data/H4_UCCSD.qasm rename to tangelo/linq/tests/data/H4_UCCSD.qasm diff --git a/tangelo/backendbuddy/tests/data/H4_qubit_hamiltonian.txt b/tangelo/linq/tests/data/H4_qubit_hamiltonian.txt similarity index 100% rename from tangelo/backendbuddy/tests/data/H4_qubit_hamiltonian.txt rename to tangelo/linq/tests/data/H4_qubit_hamiltonian.txt diff --git a/tangelo/backendbuddy/tests/data/projectq_circuit.txt b/tangelo/linq/tests/data/projectq_circuit.txt similarity index 100% rename from tangelo/backendbuddy/tests/data/projectq_circuit.txt rename to tangelo/linq/tests/data/projectq_circuit.txt diff --git a/tangelo/backendbuddy/tests/test_circuits.py b/tangelo/linq/tests/test_circuits.py similarity index 98% rename from tangelo/backendbuddy/tests/test_circuits.py rename to tangelo/linq/tests/test_circuits.py index 9feb8d9e1..5b5845d78 100644 --- a/tangelo/backendbuddy/tests/test_circuits.py +++ b/tangelo/linq/tests/test_circuits.py @@ -20,7 +20,7 @@ import unittest import copy from collections import Counter -from tangelo.backendbuddy import Gate, Circuit +from tangelo.linq import Gate, Circuit # Create several abstract circuits with different features mygates = list() diff --git a/tangelo/backendbuddy/tests/test_gates.py b/tangelo/linq/tests/test_gates.py similarity index 98% rename from tangelo/backendbuddy/tests/test_gates.py rename to tangelo/linq/tests/test_gates.py index 78a4680b8..61f894089 100644 --- a/tangelo/backendbuddy/tests/test_gates.py +++ b/tangelo/linq/tests/test_gates.py @@ -13,7 +13,7 @@ # limitations under the License. import unittest -from tangelo.backendbuddy import Gate +from tangelo.linq import Gate class TestGates(unittest.TestCase): diff --git a/tangelo/backendbuddy/tests/test_simulator.py b/tangelo/linq/tests/test_simulator.py similarity index 99% rename from tangelo/backendbuddy/tests/test_simulator.py rename to tangelo/linq/tests/test_simulator.py index b6dff1713..38f3ea3a2 100644 --- a/tangelo/backendbuddy/tests/test_simulator.py +++ b/tangelo/linq/tests/test_simulator.py @@ -23,8 +23,8 @@ import numpy as np from openfermion.ops import QubitOperator -from tangelo.backendbuddy import Gate, Circuit, translator, Simulator -from tangelo.backendbuddy.helpers import string_ham_to_of +from tangelo.linq import Gate, Circuit, translator, Simulator +from tangelo.linq.helpers import string_ham_to_of from tangelo.helpers.utils import installed_simulator, installed_sv_simulator, installed_backends path_data = os.path.dirname(os.path.abspath(__file__)) + '/data' diff --git a/tangelo/backendbuddy/tests/test_simulator_noisy.py b/tangelo/linq/tests/test_simulator_noisy.py similarity index 98% rename from tangelo/backendbuddy/tests/test_simulator_noisy.py rename to tangelo/linq/tests/test_simulator_noisy.py index f8ac19066..8a788275f 100644 --- a/tangelo/backendbuddy/tests/test_simulator_noisy.py +++ b/tangelo/linq/tests/test_simulator_noisy.py @@ -20,8 +20,8 @@ from openfermion.ops import QubitOperator -from tangelo.backendbuddy import Gate, Circuit, Simulator, backend_info -from tangelo.backendbuddy.noisy_simulation import NoiseModel, get_qiskit_noise_dict +from tangelo.linq import Gate, Circuit, Simulator, backend_info +from tangelo.linq.noisy_simulation import NoiseModel, get_qiskit_noise_dict from tangelo.helpers.utils import default_simulator, installed_backends # Noisy simulation: circuits, noise models, references diff --git a/tangelo/backendbuddy/tests/test_translator.py b/tangelo/linq/tests/test_translator.py similarity index 99% rename from tangelo/backendbuddy/tests/test_translator.py rename to tangelo/linq/tests/test_translator.py index 0a0bc5766..aa832b87d 100644 --- a/tangelo/backendbuddy/tests/test_translator.py +++ b/tangelo/linq/tests/test_translator.py @@ -21,8 +21,8 @@ import os import numpy as np -from tangelo.backendbuddy import Gate, Circuit -import tangelo.backendbuddy.translator as translator +from tangelo.linq import Gate, Circuit +import tangelo.linq.translator as translator from tangelo.helpers.utils import installed_backends path_data = os.path.dirname(os.path.realpath(__file__)) + '/data' diff --git a/tangelo/backendbuddy/translator/__init__.py b/tangelo/linq/translator/__init__.py similarity index 100% rename from tangelo/backendbuddy/translator/__init__.py rename to tangelo/linq/translator/__init__.py diff --git a/tangelo/backendbuddy/translator/qdk_template.py b/tangelo/linq/translator/qdk_template.py similarity index 100% rename from tangelo/backendbuddy/translator/qdk_template.py rename to tangelo/linq/translator/qdk_template.py diff --git a/tangelo/backendbuddy/translator/translate_braket.py b/tangelo/linq/translator/translate_braket.py similarity index 100% rename from tangelo/backendbuddy/translator/translate_braket.py rename to tangelo/linq/translator/translate_braket.py diff --git a/tangelo/backendbuddy/translator/translate_cirq.py b/tangelo/linq/translator/translate_cirq.py similarity index 100% rename from tangelo/backendbuddy/translator/translate_cirq.py rename to tangelo/linq/translator/translate_cirq.py diff --git a/tangelo/backendbuddy/translator/translate_json_ionq.py b/tangelo/linq/translator/translate_json_ionq.py similarity index 100% rename from tangelo/backendbuddy/translator/translate_json_ionq.py rename to tangelo/linq/translator/translate_json_ionq.py diff --git a/tangelo/backendbuddy/translator/translate_openqasm.py b/tangelo/linq/translator/translate_openqasm.py similarity index 99% rename from tangelo/backendbuddy/translator/translate_openqasm.py rename to tangelo/linq/translator/translate_openqasm.py index 3d3afba91..ad58c1f86 100644 --- a/tangelo/backendbuddy/translator/translate_openqasm.py +++ b/tangelo/linq/translator/translate_openqasm.py @@ -25,7 +25,7 @@ import re from math import pi -from tangelo.backendbuddy import Gate, Circuit +from tangelo.linq import Gate, Circuit def get_openqasm_gates(): diff --git a/tangelo/backendbuddy/translator/translate_projectq.py b/tangelo/linq/translator/translate_projectq.py similarity index 99% rename from tangelo/backendbuddy/translator/translate_projectq.py rename to tangelo/linq/translator/translate_projectq.py index cd714061a..f729a0c88 100644 --- a/tangelo/backendbuddy/translator/translate_projectq.py +++ b/tangelo/linq/translator/translate_projectq.py @@ -23,7 +23,7 @@ """ import re -from tangelo.backendbuddy import Gate, Circuit +from tangelo.linq import Gate, Circuit def get_projectq_gates(): diff --git a/tangelo/backendbuddy/translator/translate_qdk.py b/tangelo/linq/translator/translate_qdk.py similarity index 100% rename from tangelo/backendbuddy/translator/translate_qdk.py rename to tangelo/linq/translator/translate_qdk.py diff --git a/tangelo/backendbuddy/translator/translate_qiskit.py b/tangelo/linq/translator/translate_qiskit.py similarity index 100% rename from tangelo/backendbuddy/translator/translate_qiskit.py rename to tangelo/linq/translator/translate_qiskit.py diff --git a/tangelo/backendbuddy/translator/translate_qulacs.py b/tangelo/linq/translator/translate_qulacs.py similarity index 100% rename from tangelo/backendbuddy/translator/translate_qulacs.py rename to tangelo/linq/translator/translate_qulacs.py diff --git a/tangelo/toolboxes/ansatz_generator/_hea_circuit.py b/tangelo/toolboxes/ansatz_generator/_hea_circuit.py index c0d313e2f..7cb94de5b 100644 --- a/tangelo/toolboxes/ansatz_generator/_hea_circuit.py +++ b/tangelo/toolboxes/ansatz_generator/_hea_circuit.py @@ -14,7 +14,7 @@ """Module to create Hardware Efficient Ansatze (HEA) circuit with n_layers.""" -from tangelo.backendbuddy import Circuit, Gate +from tangelo.linq import Circuit, Gate def rotation_circuit(n_qubits, rot_type="euler"): diff --git a/tangelo/toolboxes/ansatz_generator/adapt_ansatz.py b/tangelo/toolboxes/ansatz_generator/adapt_ansatz.py index ffcce168c..9722148cd 100644 --- a/tangelo/toolboxes/ansatz_generator/adapt_ansatz.py +++ b/tangelo/toolboxes/ansatz_generator/adapt_ansatz.py @@ -16,7 +16,7 @@ import math -from tangelo.backendbuddy import Circuit +from tangelo.linq import Circuit from tangelo.toolboxes.qubit_mappings.statevector_mapping import get_reference_circuit from tangelo.toolboxes.ansatz_generator.ansatz_utils import pauliword_to_circuit from tangelo.toolboxes.ansatz_generator.ansatz import Ansatz diff --git a/tangelo/toolboxes/ansatz_generator/ansatz_utils.py b/tangelo/toolboxes/ansatz_generator/ansatz_utils.py index da049dc72..b2b546fda 100644 --- a/tangelo/toolboxes/ansatz_generator/ansatz_utils.py +++ b/tangelo/toolboxes/ansatz_generator/ansatz_utils.py @@ -18,7 +18,7 @@ """ import numpy as np -from tangelo.backendbuddy import Circuit, Gate +from tangelo.linq import Circuit, Gate def pauli_op_to_gate(index, op, inverse=False): diff --git a/tangelo/toolboxes/ansatz_generator/hea.py b/tangelo/toolboxes/ansatz_generator/hea.py index 920b6895d..6d3c75987 100644 --- a/tangelo/toolboxes/ansatz_generator/hea.py +++ b/tangelo/toolboxes/ansatz_generator/hea.py @@ -23,7 +23,7 @@ from ._hea_circuit import construct_hea_circuit from tangelo.toolboxes.qubit_mappings.mapping_transform import get_qubit_number from tangelo.toolboxes.qubit_mappings.statevector_mapping import get_reference_circuit -from tangelo.backendbuddy import Circuit +from tangelo.linq import Circuit class HEA(Ansatz): diff --git a/tangelo/toolboxes/ansatz_generator/rucc.py b/tangelo/toolboxes/ansatz_generator/rucc.py index b90d94d6d..c014a2b7a 100644 --- a/tangelo/toolboxes/ansatz_generator/rucc.py +++ b/tangelo/toolboxes/ansatz_generator/rucc.py @@ -28,7 +28,7 @@ import numpy as np -from tangelo.backendbuddy import Circuit, Gate +from tangelo.linq import Circuit, Gate from .ansatz import Ansatz diff --git a/tangelo/toolboxes/ansatz_generator/tests/test_fermionic_operators.py b/tangelo/toolboxes/ansatz_generator/tests/test_fermionic_operators.py index 7085c08eb..70910e9eb 100644 --- a/tangelo/toolboxes/ansatz_generator/tests/test_fermionic_operators.py +++ b/tangelo/toolboxes/ansatz_generator/tests/test_fermionic_operators.py @@ -14,7 +14,7 @@ import unittest -from tangelo.backendbuddy import Simulator +from tangelo.linq import Simulator from tangelo.molecule_library import mol_H2_sto3g, mol_H4_sto3g from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping from tangelo.toolboxes.qubit_mappings.statevector_mapping import get_reference_circuit, get_vector, vector_to_circuit diff --git a/tangelo/toolboxes/ansatz_generator/tests/test_hea.py b/tangelo/toolboxes/ansatz_generator/tests/test_hea.py index 6467ec67e..306dee17b 100644 --- a/tangelo/toolboxes/ansatz_generator/tests/test_hea.py +++ b/tangelo/toolboxes/ansatz_generator/tests/test_hea.py @@ -19,7 +19,7 @@ from tangelo.toolboxes.qubit_mappings import jordan_wigner from tangelo.toolboxes.ansatz_generator.hea import HEA -from tangelo.backendbuddy import Simulator +from tangelo.linq import Simulator # Initiate simulator sim = Simulator() diff --git a/tangelo/toolboxes/ansatz_generator/tests/test_penalty_terms.py b/tangelo/toolboxes/ansatz_generator/tests/test_penalty_terms.py index ca4a64047..c9374c475 100644 --- a/tangelo/toolboxes/ansatz_generator/tests/test_penalty_terms.py +++ b/tangelo/toolboxes/ansatz_generator/tests/test_penalty_terms.py @@ -14,7 +14,7 @@ import unittest -from tangelo.backendbuddy import Simulator +from tangelo.linq import Simulator from tangelo.molecule_library import mol_H2_sto3g, mol_H4_sto3g from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping from tangelo.toolboxes.qubit_mappings.statevector_mapping import get_reference_circuit, get_vector, vector_to_circuit diff --git a/tangelo/toolboxes/ansatz_generator/tests/test_uccsd.py b/tangelo/toolboxes/ansatz_generator/tests/test_uccsd.py index 654947ad1..284a357ca 100644 --- a/tangelo/toolboxes/ansatz_generator/tests/test_uccsd.py +++ b/tangelo/toolboxes/ansatz_generator/tests/test_uccsd.py @@ -18,7 +18,7 @@ from tangelo.molecule_library import mol_H2_sto3g, mol_H4_sto3g, mol_H4_doublecation_minao, mol_H4_cation_sto3g from tangelo.toolboxes.qubit_mappings import jordan_wigner from tangelo.toolboxes.ansatz_generator.uccsd import UCCSD -from tangelo.backendbuddy import Simulator +from tangelo.linq import Simulator class UCCSDTest(unittest.TestCase): diff --git a/tangelo/toolboxes/ansatz_generator/tests/test_upccgsd.py b/tangelo/toolboxes/ansatz_generator/tests/test_upccgsd.py index 8d3a706d3..97d372330 100644 --- a/tangelo/toolboxes/ansatz_generator/tests/test_upccgsd.py +++ b/tangelo/toolboxes/ansatz_generator/tests/test_upccgsd.py @@ -18,7 +18,7 @@ from tangelo.molecule_library import mol_H2_sto3g, mol_H4_doublecation_minao, mol_H4_cation_sto3g from tangelo.toolboxes.qubit_mappings import jordan_wigner from tangelo.toolboxes.ansatz_generator.upccgsd import UpCCGSD -from tangelo.backendbuddy import Simulator +from tangelo.linq import Simulator class UpCCGSDTest(unittest.TestCase): diff --git a/tangelo/toolboxes/ansatz_generator/tests/test_variational_circuit.py b/tangelo/toolboxes/ansatz_generator/tests/test_variational_circuit.py index 782738d4f..c911a1413 100644 --- a/tangelo/toolboxes/ansatz_generator/tests/test_variational_circuit.py +++ b/tangelo/toolboxes/ansatz_generator/tests/test_variational_circuit.py @@ -15,7 +15,7 @@ import unittest import numpy as np -from tangelo.backendbuddy import Gate, Circuit +from tangelo.linq import Gate, Circuit from tangelo.toolboxes.ansatz_generator.variational_circuit import VariationalCircuitAnsatz diff --git a/tangelo/toolboxes/ansatz_generator/uccsd.py b/tangelo/toolboxes/ansatz_generator/uccsd.py index 0bcd6ff10..6257e0a33 100644 --- a/tangelo/toolboxes/ansatz_generator/uccsd.py +++ b/tangelo/toolboxes/ansatz_generator/uccsd.py @@ -33,7 +33,7 @@ from pyscf import mp from openfermion.circuits import uccsd_singlet_generator -from tangelo.backendbuddy import Circuit +from tangelo.linq import Circuit from .ansatz import Ansatz from .ansatz_utils import pauliword_to_circuit diff --git a/tangelo/toolboxes/ansatz_generator/upccgsd.py b/tangelo/toolboxes/ansatz_generator/upccgsd.py index e5f06bfdd..36ce9f1c3 100644 --- a/tangelo/toolboxes/ansatz_generator/upccgsd.py +++ b/tangelo/toolboxes/ansatz_generator/upccgsd.py @@ -26,7 +26,7 @@ import numpy as np -from tangelo.backendbuddy import Circuit +from tangelo.linq import Circuit from .ansatz import Ansatz from .ansatz_utils import pauliword_to_circuit diff --git a/tangelo/toolboxes/ansatz_generator/variational_circuit.py b/tangelo/toolboxes/ansatz_generator/variational_circuit.py index d5764fa1c..fcb5cf22f 100644 --- a/tangelo/toolboxes/ansatz_generator/variational_circuit.py +++ b/tangelo/toolboxes/ansatz_generator/variational_circuit.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" This module defines an ansatz class to wrap up a custom tangelo.backendbuddy +""" This module defines an ansatz class to wrap up a custom tangelo.linq circuit. """ diff --git a/tangelo/toolboxes/measurements/qubit_terms_grouping.py b/tangelo/toolboxes/measurements/qubit_terms_grouping.py index d6e6e6cbf..05bb2b716 100644 --- a/tangelo/toolboxes/measurements/qubit_terms_grouping.py +++ b/tangelo/toolboxes/measurements/qubit_terms_grouping.py @@ -21,7 +21,7 @@ import warnings from openfermion.measurements import group_into_tensor_product_basis_sets -from tangelo.backendbuddy import Simulator +from tangelo.linq import Simulator def group_qwc(qb_ham, seed=None): diff --git a/tangelo/toolboxes/measurements/tests/data b/tangelo/toolboxes/measurements/tests/data index 6b8d12bbd..b6735529b 120000 --- a/tangelo/toolboxes/measurements/tests/data +++ b/tangelo/toolboxes/measurements/tests/data @@ -1 +1 @@ -../../../backendbuddy/tests/data \ No newline at end of file +../../../linq/tests/data \ No newline at end of file diff --git a/tangelo/toolboxes/measurements/tests/test_measurements.py b/tangelo/toolboxes/measurements/tests/test_measurements.py index 2c1acba31..a682bc202 100644 --- a/tangelo/toolboxes/measurements/tests/test_measurements.py +++ b/tangelo/toolboxes/measurements/tests/test_measurements.py @@ -16,8 +16,8 @@ import os from tangelo.helpers.utils import default_simulator -from tangelo.backendbuddy import translator, Simulator, Circuit -from tangelo.backendbuddy.helpers import string_ham_to_of, measurement_basis_gates +from tangelo.linq import translator, Simulator, Circuit +from tangelo.linq.helpers import string_ham_to_of, measurement_basis_gates from tangelo.toolboxes.operators import QubitOperator from tangelo.toolboxes.measurements import get_measurement_estimate diff --git a/tangelo/toolboxes/measurements/tests/test_qubit_terms_grouping.py b/tangelo/toolboxes/measurements/tests/test_qubit_terms_grouping.py index 8c687947c..c800c2d44 100644 --- a/tangelo/toolboxes/measurements/tests/test_qubit_terms_grouping.py +++ b/tangelo/toolboxes/measurements/tests/test_qubit_terms_grouping.py @@ -15,7 +15,7 @@ import unittest import os -from tangelo.backendbuddy import translator, Simulator, Circuit +from tangelo.linq import translator, Simulator, Circuit from tangelo.helpers import string_ham_to_of, measurement_basis_gates from tangelo.toolboxes.measurements import group_qwc, exp_value_from_measurement_bases diff --git a/tangelo/toolboxes/qubit_mappings/statevector_mapping.py b/tangelo/toolboxes/qubit_mappings/statevector_mapping.py index 6b8b6d946..28bc418a3 100644 --- a/tangelo/toolboxes/qubit_mappings/statevector_mapping.py +++ b/tangelo/toolboxes/qubit_mappings/statevector_mapping.py @@ -20,7 +20,7 @@ import numpy as np import warnings -from tangelo.backendbuddy import Gate, Circuit +from tangelo.linq import Gate, Circuit from openfermion.transforms import bravyi_kitaev_code @@ -112,7 +112,7 @@ def vector_to_circuit(vector): vector (numpy array of int): occupation vector. Returns: - Circuit: instance of tangelo.backendbuddy Circuit class. + Circuit: instance of tangelo.linq Circuit class. """ n_qubits = len(vector) @@ -141,7 +141,7 @@ def get_reference_circuit(n_spinorbitals, n_electrons, mapping, up_then_down=Fal spin (int): 2*S = n_alpha - n_beta. Returns: - Circuit: instance of tangelo.backendbuddy Circuit class. + Circuit: instance of tangelo.linq Circuit class. """ vector = get_vector(n_spinorbitals, n_electrons, mapping, up_then_down=up_then_down, spin=spin) circuit = vector_to_circuit(vector) diff --git a/tangelo/toolboxes/qubit_mappings/tests/test_statevector_mapping.py b/tangelo/toolboxes/qubit_mappings/tests/test_statevector_mapping.py index e708a0ce1..dec60c97d 100644 --- a/tangelo/toolboxes/qubit_mappings/tests/test_statevector_mapping.py +++ b/tangelo/toolboxes/qubit_mappings/tests/test_statevector_mapping.py @@ -22,7 +22,7 @@ from tangelo.toolboxes.qubit_mappings.statevector_mapping import get_vector, vector_to_circuit from tangelo.molecule_library import mol_H4_sto3g, mol_H4_cation_sto3g from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping -from tangelo.backendbuddy import Simulator +from tangelo.linq import Simulator sim = Simulator() From f54298b7a1dd6072b403e881b642f48c7d9c0239 Mon Sep 17 00:00:00 2001 From: JamesB-1qbit <84878946+JamesB-1qbit@users.noreply.github.com> Date: Mon, 6 Dec 2021 16:38:06 -0500 Subject: [PATCH 19/68] added multi-controls multi-targets, extra gates (#88) * added multi-controls multi-targets, extra gates --- tangelo/linq/circuit.py | 9 +- tangelo/linq/gate.py | 52 ++++++-- tangelo/linq/simulator.py | 5 +- tangelo/linq/tests/test_gates.py | 14 +- tangelo/linq/tests/test_simulator.py | 21 ++- tangelo/linq/tests/test_translator.py | 126 +++++++++++++++++- tangelo/linq/translator/translate_braket.py | 33 ++++- tangelo/linq/translator/translate_cirq.py | 66 ++++++--- .../linq/translator/translate_json_ionq.py | 6 +- tangelo/linq/translator/translate_projectq.py | 6 +- tangelo/linq/translator/translate_qdk.py | 31 ++++- tangelo/linq/translator/translate_qiskit.py | 35 ++++- tangelo/linq/translator/translate_qulacs.py | 74 ++++++++-- 13 files changed, 406 insertions(+), 72 deletions(-) diff --git a/tangelo/linq/circuit.py b/tangelo/linq/circuit.py index 7eda545f7..743fbbf8b 100644 --- a/tangelo/linq/circuit.py +++ b/tangelo/linq/circuit.py @@ -122,11 +122,10 @@ def check_index_valid(index): f"Gate = {gate}") # Track qubit indices - self._qubit_indices.add(gate.target) - check_index_valid(gate.target) - if isinstance(gate.control, int): - self._qubit_indices.add(gate.control) - check_index_valid(gate.control) + all_involved_qubits = gate.target if gate.control is None else gate.target + gate.control + for q in all_involved_qubits: + check_index_valid(q) + self._qubit_indices.add(q) # Keep track of the total gate count self._gate_counts[gate.name] = self._gate_counts.get(gate.name, 0) + 1 diff --git a/tangelo/linq/gate.py b/tangelo/linq/gate.py index df6ce1c65..41aa0c4ae 100644 --- a/tangelo/linq/gate.py +++ b/tangelo/linq/gate.py @@ -17,8 +17,14 @@ mathematical operation. """ -ONE_QUBIT_GATES = {"H", "X", "Y", "Z", "S", "T", "RX", "RY", "RZ"} -TWO_QUBIT_GATES = {"CNOT"} +from typing import Union + +from numpy import integer, ndarray + +ONE_QUBIT_GATES = {"H", "X", "Y", "Z", "S", "T", "RX", "RY", "RZ", "PHASE"} +TWO_QUBIT_GATES = {"CNOT", "CX", "CY", "CZ", "CRX", "CRY", "CRZ", "CPHASE", "XX", "SWAP"} +THREE_QUBIT_GATES = {"CSWAP"} +PARAMETERIZED_GATES = {"RX", "RY", "RZ", "PHASE", "CRX", "CRY", "CRZ", "CPHASE", "XX"} class Gate(dict): @@ -37,15 +43,43 @@ class Gate(dict): variational or not. """ - # TODO: extend control to a list to support gates such as the Toffoli gate etc in the future - # TODO: extend target to a list to support gates such as U2, U3 etc in the future - def __init__(self, name: str, target: int, control: int=None, parameter="", is_variational: bool=False): + def __init__(self, name: str, target: Union[int, integer, list, ndarray], + control: Union[int, integer, list, ndarray] = None, + parameter="", is_variational: bool = False): """ This gate class is basically a dictionary with extra methods. """ - if not (isinstance(target, int) and target >= 0): - raise ValueError("Qubit index must be a positive integer.") - if control and (not (isinstance(control, int) and control >= 0)): - raise ValueError("Qubit index must be a positive integer.") + def check_qubit_indices(qubit_list, label): + """Function to check if all given qubit indices are positive integers + + Args: + qubit_list (list) :: List of values to check are positive integers + label (str) :: The label of the list, "control" or "target" + + Raises: + ValueError :: If any of the values in the list are not non-negative integers. + """ + errmsg = "" + for ind in qubit_list: + if (type(ind) != int) or (ind < 0): + errmsg += f"\n {label} qubit index {ind} is not a non-negative integer" + + if errmsg: + raise ValueError(f"Error: type or value of {label} qubit indices not as expected. See details below:"+errmsg) + + target = (target.tolist() if isinstance(target, ndarray) else list(target)) if hasattr(target, "__iter__") else [target] + + check_qubit_indices(target, "target") + + if control is not None: + if name[0] != "C": + raise ValueError(f"Gate {name} was given control={control} but does not support controls. Try C{name}") + control = (control.tolist() if isinstance(control, ndarray) else list(control)) if hasattr(control, "__iter__") else [control] + check_qubit_indices(control, "control") + + # Check for duplications in qubits + all_involved_qubits = target if control is None else target + control + if len(all_involved_qubits) != len(set(all_involved_qubits)): + raise ValueError(f"There are duplicate qubits in the target/control qubits") self.__dict__ = {"name": name, "target": target, "control": control, "parameter": parameter, "is_variational": is_variational} diff --git a/tangelo/linq/simulator.py b/tangelo/linq/simulator.py index 7116010fe..300f9c2ac 100644 --- a/tangelo/linq/simulator.py +++ b/tangelo/linq/simulator.py @@ -202,9 +202,8 @@ def simulate(self, source_circuit, return_statevector=False, initial_statevector # Noiseless simulation using the statevector simulator otherwise else: backend = qiskit.Aer.get_backend("aer_simulator", method='statevector') - save_state_circuit = qiskit.QuantumCircuit(source_circuit.width, source_circuit.width) - save_state_circuit.save_statevector() - translated_circuit = translated_circuit.compose(save_state_circuit) + translated_circuit = qiskit.transpile(translated_circuit, backend) + translated_circuit.save_statevector() sim_results = backend.run(translated_circuit).result() self._current_state = sim_results.get_statevector(translated_circuit) frequencies = self._statevector_to_frequencies(self._current_state) diff --git a/tangelo/linq/tests/test_gates.py b/tangelo/linq/tests/test_gates.py index 61f894089..2c826d312 100644 --- a/tangelo/linq/tests/test_gates.py +++ b/tangelo/linq/tests/test_gates.py @@ -13,6 +13,9 @@ # limitations under the License. import unittest + +import numpy as np + from tangelo.linq import Gate @@ -34,8 +37,10 @@ def test_some_gates(self): RX_gate = Gate("RX", 1, parameter=2.) # Create a parameterized rotation on qubit 1 , with an undefined angle, that will be variational RZ_gate = Gate("RZ", 1, parameter="an expression", is_variational=True) + # Create a multi-controlled X gate with a numpy array + CCCX_gate = Gate("CX", 0, control=np.array([1, 2, 4], dtype=np.int32)) - for gate in [H_gate, CNOT_gate, RX_gate, RZ_gate]: + for gate in [H_gate, CNOT_gate, RX_gate, RZ_gate, CCCX_gate]: print(gate) def test_incorrect_gate(self): @@ -43,6 +48,13 @@ def test_incorrect_gate(self): self.assertRaises(ValueError, Gate, "H", -1) self.assertRaises(ValueError, Gate, "CNOT", 0, control=0.3) + self.assertRaises(ValueError, Gate, 'X', target=0, control=1) + self.assertRaises(ValueError, Gate, "CNOT", target=0, control=0) + + def test_integer_types(self): + """ Test to catch error with incorrect target or control qubit index type""" + self.assertRaises(ValueError, Gate, "CSWAP", target=[0, 'a'], control=np.array([1], dtype=np.int32)) + self.assertRaises(ValueError, Gate, "X", target=0, control=[-1, 2, 3],) if __name__ == "__main__": diff --git a/tangelo/linq/tests/test_simulator.py b/tangelo/linq/tests/test_simulator.py index 38f3ea3a2..1c6f6592d 100644 --- a/tangelo/linq/tests/test_simulator.py +++ b/tangelo/linq/tests/test_simulator.py @@ -24,6 +24,7 @@ from openfermion.ops import QubitOperator from tangelo.linq import Gate, Circuit, translator, Simulator +from tangelo.linq.gate import PARAMETERIZED_GATES from tangelo.linq.helpers import string_ham_to_of from tangelo.helpers.utils import installed_simulator, installed_sv_simulator, installed_backends @@ -40,6 +41,18 @@ circuit3 = Circuit([Gate("RX", 0, parameter=2.), Gate("RY", 1, parameter=-1.)]) # Circuit for the parametrized rotation gate Rz. Some convention about the sign of theta or a phase may appear circuit4 = Circuit([Gate("RZ", 0, parameter=2.)], n_qubits=2) +# Circuit that tests all gates that are supported on all simulators +init_gates = [Gate('H', 0), Gate('X', 1), Gate('H', 2)] +one_qubit_gate_names = ["H", "X", "Y", "Z", "S", "T", "RX", "RY", "RZ", "PHASE"] +one_qubit_gates = [Gate(name, target=0) if name not in PARAMETERIZED_GATES else Gate(name, target=0, parameter=0.5) + for name in one_qubit_gate_names] +one_qubit_gates += [Gate(name, target=1) if name not in PARAMETERIZED_GATES else Gate(name, target=1, parameter=0.2) + for name in one_qubit_gate_names] +two_qubit_gate_names = ["CNOT", "CH", "CX", "CY", "CZ", "CRX", "CRY", "CRZ", "CPHASE"] +two_qubit_gates = [Gate(name, target=1, control=0) if name not in PARAMETERIZED_GATES + else Gate(name, target=1, control=0, parameter=0.5) for name in two_qubit_gate_names] +swap_gates = [Gate('SWAP', target=[1, 0]), Gate('CSWAP', target=[1, 2], control=0)] +circuit5 = Circuit(init_gates + one_qubit_gates + two_qubit_gates + swap_gates) # Circuit preparing a mixed-state (e.g containing a MEASURE instruction in the middle of the circuit) circuit_mixed = Circuit([Gate("RX", 0, parameter=2.), Gate("RY", 1, parameter=-1.), Gate("MEASURE", 0), Gate("X", 0)]) @@ -49,7 +62,7 @@ op3 = 1.0 * QubitOperator('Y0') - 2.0 * QubitOperator('Z0 X1') # Linear combination op4 = 1.0 * QubitOperator('Z0 Y1 X2') # Operates on more qubits than circuit size -circuits = [circuit1, circuit2, circuit3, circuit4] +circuits = [circuit1, circuit2, circuit3, circuit4, circuit5] ops = [op1, op2, op3] # Reference results @@ -58,7 +71,11 @@ ref_freqs.append({'01': 0.5, '10': 0.5}) ref_freqs.append({'00': 0.2248275934887, '10': 0.5453235594453, '01': 0.06709898823771, '11': 0.16274985882821}) ref_freqs.append({'00': 1.0}) -reference_exp_values = np.array([[0., 0., 0.], [0., -1., 0.], [-0.41614684, 0.7651474, -1.6096484], [1., 0., 0.]]) +ref_freqs.append({'000': 0.15972060437359714, '100': 0.2828171838599203, '010': 0.03984122195648572, + '110': 0.28281718385992016, '001': 0.15972060437359714, '101': 0.017620989809996816, + '011': 0.039841221956485706, '111': 0.01762098980999681}) +reference_exp_values = np.array([[0., 0., 0.], [0., -1., 0.], [-0.41614684, 0.7651474, -1.6096484], [1., 0., 0.], + [-0.20175269, -0.0600213, 1.2972912]]) reference_mixed = {'01': 0.163, '11': 0.066, '10': 0.225, '00': 0.545} # With Qiskit noiseless, 1M shots diff --git a/tangelo/linq/tests/test_translator.py b/tangelo/linq/tests/test_translator.py index aa832b87d..70dbd7e92 100644 --- a/tangelo/linq/tests/test_translator.py +++ b/tangelo/linq/tests/test_translator.py @@ -22,6 +22,7 @@ import numpy as np from tangelo.linq import Gate, Circuit +from tangelo.linq.gate import PARAMETERIZED_GATES import tangelo.linq.translator as translator from tangelo.helpers.utils import installed_backends @@ -29,7 +30,28 @@ gates = [Gate("H", 2), Gate("CNOT", 1, control=0), Gate("CNOT", 2, control=1), Gate("Y", 0), Gate("S", 0)] abs_circ = Circuit(gates) + Circuit([Gate("RX", 1, parameter=2.)]) +multi_controlled_gates = [Gate("X", 0), Gate("X", 1), Gate("CX", target=2, control=[0, 1])] +abs_multi_circ = Circuit(multi_controlled_gates) +init_gates = [Gate('H', 0), Gate('X', 1), Gate('H', 2)] +one_qubit_gate_names = ["H", "X", "Y", "Z", "S", "T", "RX", "RY", "RZ", "PHASE"] +one_qubit_gates = [Gate(name, target=0) if name not in PARAMETERIZED_GATES else Gate(name, target=0, parameter=0.5) + for name in one_qubit_gate_names] +one_qubit_gates += [Gate(name, target=1) if name not in PARAMETERIZED_GATES else Gate(name, target=1, parameter=0.2) + for name in one_qubit_gate_names] +two_qubit_gate_names = ["CNOT", "CX", "CY", "CZ", "CPHASE", "CRZ"] +two_qubit_gates = [Gate(name, target=1, control=0) if name not in PARAMETERIZED_GATES + else Gate(name, target=1, control=0, parameter=0.5) for name in two_qubit_gate_names] +swap_gates = [Gate('SWAP', target=[1, 0]), Gate('CSWAP', target=[1, 2], control=0)] +big_circuit = Circuit(init_gates + one_qubit_gates + two_qubit_gates + swap_gates + [Gate('XX', [0, 1], parameter=0.5)]) + references = [0., 0.38205142 ** 2, 0., 0.59500984 ** 2, 0., 0.38205142 ** 2, 0., 0.59500984 ** 2] +references_multi = [0., 0., 0., 0., 0., 0., 0., 1.] +reference_big_lsq = [-0.29022980 + 0.20684454j, -0.34400320 + 0.12534970j, 0.21316957 + 0.23442923j, + 0.15939614 + 0.15293439j, -0.36723378 + 0.29031223j, -0.04807413 + 0.0797184j, + -0.37427732 + 0.41885117j, -0.05511766 + 0.20825736j] +reference_big_msq = [-0.29022979 + 0.20684454j, -0.36723376 + 0.29031221j, 0.21316958 + 0.23442923j, + -0.37427729 + 0.41885117j, -0.34400321 + 0.12534970j, -0.04807414 + 0.07971841j, + 0.15939615 + 0.15293440j, -0.05511766 + 0.20825737j] abs_circ_mixed = Circuit(gates) + Circuit([Gate("RX", 1, parameter=1.5), Gate("MEASURE", 0)]) @@ -68,6 +90,38 @@ def test_qulacs(self): # Assert that both simulations returned the same state vector np.testing.assert_array_equal(state1.get_vector(), state2.get_vector()) + # Generates the qulacs circuit by translating from the abstract one + translated_circuit = translator.translate_qulacs(abs_multi_circ) + + # Run the simulation + state1 = qulacs.QuantumState(abs_multi_circ.width) + translated_circuit.update_quantum_state(state1) + + # Directly define the same circuit through qulacs + # NB: this includes convention fixes for some parametrized rotation gates (-theta instead of theta) + qulacs_circuit = qulacs.QuantumCircuit(3) + qulacs_circuit.add_X_gate(0) + qulacs_circuit.add_X_gate(1) + mat_gate = qulacs.gate.to_matrix_gate(qulacs.gate.X(2)) + mat_gate.add_control_qubit(0, 1) + mat_gate.add_control_qubit(1, 1) + qulacs_circuit.add_gate(mat_gate) + + # Run the simulation + state2 = qulacs.QuantumState(abs_multi_circ.width) + qulacs_circuit.update_quantum_state(state2) + + # Assert that both simulations returned the same state vector + np.testing.assert_array_equal(state1.get_vector(), state2.get_vector()) + + # Test that the translated circuit reports the same result for all cross-supported gates + translated_circuit = translator.translate_qulacs(big_circuit) + + # Run the simulation + state1 = qulacs.QuantumState(big_circuit.width) + translated_circuit.update_quantum_state(state1) + np.testing.assert_array_almost_equal(state1.get_vector(), reference_big_msq, decimal=6) + @unittest.skipIf("qiskit" not in installed_backends, "Test Skipped: Backend not available \n") def test_qiskit(self): """ @@ -90,11 +144,11 @@ def test_qiskit(self): circ.rx(2., 1) # Simulate both circuits, assert state vectors are equal - qiskit_simulator = qiskit.Aer.get_backend("aer_simulator", method='statevector') - save_state_circuit = qiskit.QuantumCircuit(abs_circ.width, abs_circ.width) - save_state_circuit.save_statevector() - translated_circuit = translated_circuit.compose(save_state_circuit) - circ = circ.compose(save_state_circuit) + qiskit_simulator = qiskit.Aer.get_backend("aer_simulator", method='statevector') + translated_circuit = qiskit.transpile(translated_circuit, qiskit_simulator) + circ = qiskit.transpile(circ, qiskit_simulator) + translated_circuit.save_statevector() + circ.save_statevector() sim_results = qiskit_simulator.run(translated_circuit).result() v1 = sim_results.get_statevector(translated_circuit) @@ -104,6 +158,18 @@ def test_qiskit(self): np.testing.assert_array_equal(v1, v2) + # Return error when attempting to use qiskit with multiple controls + self.assertRaises(ValueError, translator.translate_qiskit, abs_multi_circ) + + # Generate the qiskit circuit by translating from the abstract one and print it + translated_circuit = translator.translate_qiskit(big_circuit) + # Simulate both circuits, assert state vectors are equal + qiskit_simulator = qiskit.Aer.get_backend("aer_simulator", method='statevector') + translated_circuit = qiskit.transpile(translated_circuit, qiskit_simulator) + translated_circuit.save_statevector() + sim_results = qiskit_simulator.run(translated_circuit).result() + np.testing.assert_array_almost_equal(sim_results.get_statevector(translated_circuit), reference_big_msq, decimal=6) + @unittest.skipIf("cirq" not in installed_backends, "Test Skipped: Backend not available \n") def test_cirq(self): """ @@ -138,6 +204,26 @@ def test_cirq(self): np.testing.assert_array_equal(v1, v2) + translated_circuit = translator.translate_cirq(abs_multi_circ) + circ = cirq.Circuit() + circ.append(cirq.X(qubit_labels[0])) + circ.append(cirq.X(qubit_labels[1])) + next_gate = cirq.X.controlled(num_controls=2) + circ.append(next_gate(qubit_labels[0], qubit_labels[1], qubit_labels[2])) + + job_sim = cirq_simulator.simulate(circ) + v1 = job_sim.final_state_vector + + job_sim = cirq_simulator.simulate(translated_circuit) + v2 = job_sim.final_state_vector + + np.testing.assert_array_equal(v1, v2) + + # Test that translated circuit is correct for all cross-supported gates + translated_circuit = translator.translate_cirq(big_circuit) + job_sim = cirq_simulator.simulate(translated_circuit) + np.testing.assert_array_almost_equal(job_sim.final_state_vector, reference_big_lsq, decimal=6) + @unittest.skipIf("qdk" not in installed_backends, "Test Skipped: Backend not available \n") def test_qdk(self): """ Compares the frequencies computed by the QDK/Q# shot-based simulator to the theoretical ones """ @@ -163,6 +249,27 @@ def test_qdk(self): # Compares with theoretical probabilities obtained through a statevector simulator np.testing.assert_almost_equal(np.array(probabilities), np.array(references), 2) + # Generate the qdk circuit by translating from the abstract one and print it + translated_circuit = translator.translate_qsharp(abs_multi_circ) + print(translated_circuit) + + # Write to file + with open('tmp_circuit.qs', 'w+') as f_out: + f_out.write(translated_circuit) + + # Compile all qsharp files found in directory and import the qsharp operation + import qsharp + qsharp.reload() + from MyNamespace import EstimateFrequencies + + # Simulate, return frequencies + n_shots = 10**4 + probabilities = EstimateFrequencies.simulate(nQubits=abs_multi_circ.width, nShots=n_shots) + print("Q# frequency estimation with {0} samples: \n {1}".format(n_shots, probabilities)) + + # Compares with theoretical probabilities obtained through a statevector simulator + np.testing.assert_almost_equal(np.array(probabilities), np.array(references_multi), 2) + @unittest.skipIf("projectq" not in installed_backends, "Test Skipped: Backend not available \n") def test_projectq(self): """ Compares state vector of native ProjectQ circuit against translated one """ @@ -300,6 +407,15 @@ def test_braket(self): np.testing.assert_array_equal(circ_result.values[0], translated_result.values[0]) + # Return error when attempting to use braket with multiple controls + self.assertRaises(ValueError, translator.translate_braket, abs_multi_circ) + + # Test that circuit is correct for all cross-supported gates + translated_circuit = translator.translate_braket(big_circuit) + translated_circuit.state_vector() + translated_result = device.run(translated_circuit, shots=0).result() + np.testing.assert_array_almost_equal(translated_result.values[0], reference_big_lsq, decimal=6) + @unittest.skipIf("qiskit" not in installed_backends, "Test Skipped: Backend not available \n") def test_unsupported_gate(self): """ Must return an error if a gate is not supported for the target backend """ diff --git a/tangelo/linq/translator/translate_braket.py b/tangelo/linq/translator/translate_braket.py index a88a7d6df..dbd7e4ce0 100644 --- a/tangelo/linq/translator/translate_braket.py +++ b/tangelo/linq/translator/translate_braket.py @@ -36,12 +36,21 @@ def get_braket_gates(): GATE_BRAKET["X"] = BraketCircuit.x GATE_BRAKET["Y"] = BraketCircuit.y GATE_BRAKET["Z"] = BraketCircuit.z + GATE_BRAKET["CX"] = BraketCircuit.cnot + GATE_BRAKET["CY"] = BraketCircuit.cy + GATE_BRAKET["CZ"] = BraketCircuit.cz GATE_BRAKET["S"] = BraketCircuit.s GATE_BRAKET["T"] = BraketCircuit.t GATE_BRAKET["RX"] = BraketCircuit.rx GATE_BRAKET["RY"] = BraketCircuit.ry GATE_BRAKET["RZ"] = BraketCircuit.rz + GATE_BRAKET["XX"] = BraketCircuit.xx + GATE_BRAKET["CRZ"] = [BraketCircuit.cphaseshift, BraketCircuit.cphaseshift10] + GATE_BRAKET["PHASE"] = BraketCircuit.phaseshift + GATE_BRAKET["CPHASE"] = BraketCircuit.cphaseshift GATE_BRAKET["CNOT"] = BraketCircuit.cnot + GATE_BRAKET["SWAP"] = BraketCircuit.swap + GATE_BRAKET["CSWAP"] = BraketCircuit.cswap # GATE_BRAKET["MEASURE"] = ? (mid-circuit measurement currently unsupported?) return GATE_BRAKET @@ -65,12 +74,26 @@ def translate_braket(source_circuit): # Map the gate information properly. Different for each backend (order, values) for gate in source_circuit._gates: + if gate.control is not None: + if len(gate.control) > 1: + raise ValueError('Multi-controlled gates not supported with braket: Gate {gate.name} with controls {gate.control} is invalid') if gate.name in {"H", "X", "Y", "Z", "S", "T"}: - (GATE_BRAKET[gate.name])(target_circuit, gate.target) - elif gate.name in {"RX", "RY", "RZ"}: - (GATE_BRAKET[gate.name])(target_circuit, gate.target, gate.parameter) - elif gate.name in {"CNOT"}: - (GATE_BRAKET[gate.name])(target_circuit, control=gate.control, target=gate.target) + (GATE_BRAKET[gate.name])(target_circuit, gate.target[0]) + elif gate.name in {"RX", "RY", "RZ", "PHASE"}: + (GATE_BRAKET[gate.name])(target_circuit, gate.target[0], gate.parameter) + elif gate.name in {"CNOT", "CX", "CY", "CZ"}: + (GATE_BRAKET[gate.name])(target_circuit, control=gate.control[0], target=gate.target[0]) + elif gate.name in {"XX"}: + (GATE_BRAKET[gate.name])(target_circuit, gate.target[0], gate.target[1], gate.parameter) + elif gate.name in {"CRZ"}: + (GATE_BRAKET[gate.name][0])(target_circuit, gate.control[0], gate.target[0], gate.parameter/2.) + (GATE_BRAKET[gate.name][1])(target_circuit, gate.control[0], gate.target[0], -gate.parameter/2.) + elif gate.name in {"SWAP"}: + (GATE_BRAKET[gate.name])(target_circuit, gate.target[0], gate.target[1]) + elif gate.name in {"CSWAP"}: + (GATE_BRAKET[gate.name])(target_circuit, gate.control[0], gate.target[0], gate.target[1]) + elif gate.name in {"CPHASE"}: + (GATE_BRAKET[gate.name])(target_circuit, gate.control[0], gate.target[0], gate.parameter) # elif gate.name in {"MEASURE"}: # implement if mid-circuit measurement available through Braket later on else: diff --git a/tangelo/linq/translator/translate_cirq.py b/tangelo/linq/translator/translate_cirq.py index e9c4f27fe..9cfe94036 100644 --- a/tangelo/linq/translator/translate_cirq.py +++ b/tangelo/linq/translator/translate_cirq.py @@ -21,6 +21,7 @@ - how the order and conventions for some of the inputs to the gate operations may also differ. """ +from math import pi def get_cirq_gates(): @@ -34,12 +35,24 @@ def get_cirq_gates(): GATE_CIRQ["X"] = cirq.X GATE_CIRQ["Y"] = cirq.Y GATE_CIRQ["Z"] = cirq.Z + GATE_CIRQ["CX"] = cirq.X + GATE_CIRQ["CY"] = cirq.Y + GATE_CIRQ["CZ"] = cirq.Z GATE_CIRQ["S"] = cirq.S GATE_CIRQ["T"] = cirq.T + GATE_CIRQ["CH"] = cirq.H GATE_CIRQ["RX"] = cirq.rx GATE_CIRQ["RY"] = cirq.ry GATE_CIRQ["RZ"] = cirq.rz GATE_CIRQ["CNOT"] = cirq.CNOT + GATE_CIRQ["CRZ"] = cirq.rz + GATE_CIRQ["CRX"] = cirq.rx + GATE_CIRQ["CRY"] = cirq.ry + GATE_CIRQ["PHASE"] = cirq.ZPowGate + GATE_CIRQ["CPHASE"] = cirq.ZPowGate + GATE_CIRQ["XX"] = cirq.XXPowGate + GATE_CIRQ["SWAP"] = cirq.SWAP + GATE_CIRQ["CSWAP"] = cirq.SWAP GATE_CIRQ["MEASURE"] = cirq.measure return GATE_CIRQ @@ -69,15 +82,38 @@ def translate_cirq(source_circuit, noise_model=None): # Maps the gate information properly. Different for each backend (order, values) for gate in source_circuit._gates: + if (gate.control is not None) and gate.name != 'CNOT': + num_controls = len(gate.control) + control_list = [qubit_list[c] for c in gate.control] if gate.name in {"H", "X", "Y", "Z", "S", "T"}: - target_circuit.append(GATE_CIRQ[gate.name](qubit_list[gate.target])) + target_circuit.append(GATE_CIRQ[gate.name](qubit_list[gate.target[0]])) + elif gate.name in {"CH", "CX", "CY", "CZ"}: + next_gate = GATE_CIRQ[gate.name].controlled(num_controls) + target_circuit.append(next_gate(*control_list, qubit_list[gate.target[0]])) elif gate.name in {"RX", "RY", "RZ"}: next_gate = GATE_CIRQ[gate.name](gate.parameter) - target_circuit.append(next_gate(qubit_list[gate.target])) + target_circuit.append(next_gate(qubit_list[gate.target[0]])) elif gate.name in {"CNOT"}: - target_circuit.append(GATE_CIRQ[gate.name](qubit_list[gate.control], qubit_list[gate.target])) + target_circuit.append(GATE_CIRQ[gate.name](qubit_list[gate.control[0]], qubit_list[gate.target[0]])) elif gate.name in {"MEASURE"}: - target_circuit.append(GATE_CIRQ[gate.name](qubit_list[gate.target])) + target_circuit.append(GATE_CIRQ[gate.name](qubit_list[gate.target[0]])) + elif gate.name in {"CRZ", "CRX", "CRY"}: + next_gate = GATE_CIRQ[gate.name](gate.parameter).controlled(num_controls) + target_circuit.append(next_gate(*control_list, qubit_list[gate.target[0]])) + elif gate.name in {"XX"}: + next_gate = GATE_CIRQ[gate.name](exponent=gate.parameter/pi, global_shift=-0.5) + target_circuit.append(next_gate(qubit_list[gate.target[0]], qubit_list[gate.target[1]])) + elif gate.name in {"PHASE"}: + next_gate = GATE_CIRQ[gate.name](exponent=gate.parameter/pi) + target_circuit.append(next_gate(qubit_list[gate.target[0]])) + elif gate.name in {"CPHASE"}: + next_gate = GATE_CIRQ[gate.name](exponent=gate.parameter/pi).controlled(num_controls) + target_circuit.append(next_gate(*control_list, qubit_list[gate.target[0]])) + elif gate.name in {"SWAP"}: + target_circuit.append(GATE_CIRQ[gate.name](qubit_list[gate.target[0]], qubit_list[gate.target[1]])) + elif gate.name in {"CSWAP"}: + next_gate = GATE_CIRQ[gate.name].controlled(num_controls) + target_circuit.append(next_gate(*control_list, qubit_list[gate.target[0]], qubit_list[gate.target[1]])) else: raise ValueError(f"Gate '{gate.name}' not supported on backend cirq") @@ -87,17 +123,17 @@ def translate_cirq(source_circuit, noise_model=None): if nt == 'pauli': # Define pauli gate in cirq language depo = cirq.asymmetric_depolarize(np[0], np[1], np[2]) - target_circuit.append(depo(qubit_list[gate.target])) - if gate.control or gate.control == 0: - target_circuit.append(depo(qubit_list[gate.control])) + + target_circuit += [depo(qubit_list[t]) for t in gate.target] + if gate.control is not None: + target_circuit += [depo(qubit_list[c]) for c in gate.control] elif nt == 'depol': - if gate.control or gate.control == 0: - # define 2-qubit depolarization gate - depo = cirq.depolarize(np*15/16, 2) # sparam, num_qubits - target_circuit.append(depo(qubit_list[gate.control], qubit_list[gate.target])) # gates targetted - else: - # sdefine 1-qubit depolarization gate - depo = cirq.depolarize(np*3/4, 1) - target_circuit.append(depo(qubit_list[gate.target])) + depo_list = [qubit_list[t] for t in gate.target] + if gate.control is not None: + depo_list += [qubit_list[c] for c in gate.control] + depo_size = len(depo_list) + # define depo_size-qubit depolarization gate + depo = cirq.depolarize(np*(4**depo_size-1)/4**depo_size, depo_size) # param, num_qubits + target_circuit.append(depo(*depo_list)) # gates targeted return target_circuit diff --git a/tangelo/linq/translator/translate_json_ionq.py b/tangelo/linq/translator/translate_json_ionq.py index e387d5e7e..78c981682 100644 --- a/tangelo/linq/translator/translate_json_ionq.py +++ b/tangelo/linq/translator/translate_json_ionq.py @@ -54,11 +54,11 @@ def translate_json_ionq(source_circuit): json_gates = [] for gate in source_circuit._gates: if gate.name in {"H", "X", "Y", "Z", "S", "T"}: - json_gates.append({'gate': GATE_JSON_IONQ[gate.name], 'target': gate.target}) + json_gates.append({'gate': GATE_JSON_IONQ[gate.name], 'target': gate.target[0]}) elif gate.name in {"RX", "RY", "RZ"}: - json_gates.append({'gate': GATE_JSON_IONQ[gate.name], 'target': gate.target, 'rotation': gate.parameter}) + json_gates.append({'gate': GATE_JSON_IONQ[gate.name], 'target': gate.target[0], 'rotation': gate.parameter}) elif gate.name in {"CNOT"}: - json_gates.append({'gate': GATE_JSON_IONQ[gate.name], 'target': gate.target, 'control': gate.control}) + json_gates.append({'gate': GATE_JSON_IONQ[gate.name], 'target': gate.target[0], 'control': gate.control[0]}) else: raise ValueError(f"Gate '{gate.name}' not supported with JSON IonQ translation") diff --git a/tangelo/linq/translator/translate_projectq.py b/tangelo/linq/translator/translate_projectq.py index f729a0c88..ec8ebdd27 100644 --- a/tangelo/linq/translator/translate_projectq.py +++ b/tangelo/linq/translator/translate_projectq.py @@ -62,11 +62,11 @@ def translate_projectq(source_circuit): for gate in source_circuit._gates: if gate.name in {"H", "X", "Y", "Z", "S", "T", "MEASURE"}: - projectq_circuit += f"{GATE_PROJECTQ[gate.name]} | Qureg[{gate.target}]\n" + projectq_circuit += f"{GATE_PROJECTQ[gate.name]} | Qureg[{gate.target[0]}]\n" elif gate.name in {"RX", "RY", "RZ"}: - projectq_circuit += f"{GATE_PROJECTQ[gate.name]}({gate.parameter}) | Qureg[{gate.target}]\n" + projectq_circuit += f"{GATE_PROJECTQ[gate.name]}({gate.parameter}) | Qureg[{gate.target[0]}]\n" elif gate.name in {"CNOT"}: - projectq_circuit += f"{GATE_PROJECTQ[gate.name]} | ( Qureg[{gate.control}], Qureg[{gate.target}] )\n" + projectq_circuit += f"{GATE_PROJECTQ[gate.name]} | ( Qureg[{gate.control[0]}], Qureg[{gate.target[0]}] )\n" else: raise ValueError(f"Gate '{gate.name}' not supported on backend projectQ") diff --git a/tangelo/linq/translator/translate_qdk.py b/tangelo/linq/translator/translate_qdk.py index 541e41691..b3086b00b 100644 --- a/tangelo/linq/translator/translate_qdk.py +++ b/tangelo/linq/translator/translate_qdk.py @@ -34,6 +34,13 @@ def get_qdk_gates(): GATE_QDK[name] = name for name in {"RX", "RY", "RZ"}: GATE_QDK[name] = name[0] + name[1:].lower() + GATE_QDK["PHASE"] = "R1" + GATE_QDK["CPHASE"] = "R1" + for name in {"CRX", "CRY", "CRZ"}: + GATE_QDK[name] = name[1] + name[2:].lower() + for name in {"CH", "CX", "CY", "CZ", "CS", "CT", "CSWAP"}: + GATE_QDK[name] = name[1:] + GATE_QDK["SWAP"] = "SWAP" GATE_QDK["MEASURE"] = "M" return GATE_QDK @@ -66,14 +73,28 @@ def translate_qsharp(source_circuit, operation="MyQsharpOperation"): # Generate Q# strings with the right syntax, order and values for the gate inputs body_str = "" for gate in source_circuit._gates: + if gate.control is not None and gate.name != "CNOT": + control_string = '[' + num_controls = len(gate.control) + for i, c in enumerate(gate.control): + control_string += f'qreg[{c}]]' if i == num_controls - 1 else f'qreg[{c}], ' + if gate.name in {"H", "X", "Y", "Z", "S", "T"}: - body_str += f"\t\t{GATE_QDK[gate.name]}(qreg[{gate.target}]);\n" - elif gate.name in {"RX", "RY", "RZ"}: - body_str += f"\t\t{GATE_QDK[gate.name]}({gate.parameter}, qreg[{gate.target}]);\n" + body_str += f"\t\t{GATE_QDK[gate.name]}(qreg[{gate.target[0]}]);\n" + elif gate.name in {"RX", "RY", "RZ", "PHASE"}: + body_str += f"\t\t{GATE_QDK[gate.name]}({gate.parameter}, qreg[{gate.target[0]}]);\n" elif gate.name in {"CNOT"}: - body_str += f"\t\t{GATE_QDK[gate.name]}(qreg[{gate.control}], qreg[{gate.target}]);\n" + body_str += f"\t\t{GATE_QDK[gate.name]}(qreg[{gate.control[0]}], qreg[{gate.target[0]}]);\n" + elif gate.name in {"CRX", "CRY", "CRZ", "CPHASE"}: + body_str += f"\t\tControlled {GATE_QDK[gate.name]}({control_string}, ({gate.parameter}, qreg[{gate.target[0]}]));\n" + elif gate.name in {"CH", "CX", "CY", "CZ", "CS", "CT"}: + body_str += f"\t\tControlled {GATE_QDK[gate.name]}({control_string}, (qreg[{gate.target[0]}]));\n" + elif gate.name in {"SWAP"}: + body_str += f"\t\t{GATE_QDK[gate.name]}(qreg[{gate.target[0]}], qreg[{gate.target[1]}]);\n" + elif gate.name in {"CSWAP"}: + body_str += f"\t\tControlled {GATE_QDK[gate.name]}({control_string}, (qreg[{gate.target[0]}], qreg[{gate.target[1]}]));\n" elif gate.name in {"MEASURE"}: - body_str += f"\t\tset c w/= {gate.target} <- {GATE_QDK[gate.name]}(qreg[{gate.target}]);\n" + body_str += f"\t\tset c w/= {gate.target[0]} <- {GATE_QDK[gate.name]}(qreg[{gate.target[0]}]);\n" else: raise ValueError(f"Gate '{gate.name}' not supported on backend qdk") qsharp_string += body_str + "\n\t\treturn ForEach(MResetZ, qreg);\n" diff --git a/tangelo/linq/translator/translate_qiskit.py b/tangelo/linq/translator/translate_qiskit.py index 597701193..eb972bd53 100644 --- a/tangelo/linq/translator/translate_qiskit.py +++ b/tangelo/linq/translator/translate_qiskit.py @@ -36,12 +36,24 @@ def get_qiskit_gates(): GATE_QISKIT["X"] = qiskit.QuantumCircuit.x GATE_QISKIT["Y"] = qiskit.QuantumCircuit.y GATE_QISKIT["Z"] = qiskit.QuantumCircuit.z + GATE_QISKIT["CH"] = qiskit.QuantumCircuit.ch + GATE_QISKIT["CX"] = qiskit.QuantumCircuit.cx + GATE_QISKIT["CY"] = qiskit.QuantumCircuit.cy + GATE_QISKIT["CZ"] = qiskit.QuantumCircuit.cz GATE_QISKIT["S"] = qiskit.QuantumCircuit.s GATE_QISKIT["T"] = qiskit.QuantumCircuit.t GATE_QISKIT["RX"] = qiskit.QuantumCircuit.rx GATE_QISKIT["RY"] = qiskit.QuantumCircuit.ry GATE_QISKIT["RZ"] = qiskit.QuantumCircuit.rz + GATE_QISKIT["CRX"] = qiskit.QuantumCircuit.crx + GATE_QISKIT["CRY"] = qiskit.QuantumCircuit.cry + GATE_QISKIT["CRZ"] = qiskit.QuantumCircuit.crz GATE_QISKIT["CNOT"] = qiskit.QuantumCircuit.cx + GATE_QISKIT["SWAP"] = qiskit.QuantumCircuit.swap + GATE_QISKIT["XX"] = qiskit.QuantumCircuit.rxx + GATE_QISKIT["CSWAP"] = qiskit.QuantumCircuit.cswap + GATE_QISKIT["PHASE"] = qiskit.QuantumCircuit.p + GATE_QISKIT["CPHASE"] = qiskit.QuantumCircuit.cp GATE_QISKIT["MEASURE"] = qiskit.QuantumCircuit.measure return GATE_QISKIT @@ -64,14 +76,25 @@ def translate_qiskit(source_circuit): # Maps the gate information properly. Different for each backend (order, values) for gate in source_circuit._gates: + if gate.control is not None: + if len(gate.control) > 1: + raise ValueError('Multi-controlled gates not supported with qiskit. Gate {gate.name} with controls {gate.control} is not allowed') if gate.name in {"H", "X", "Y", "Z", "S", "T"}: - (GATE_QISKIT[gate.name])(target_circuit, gate.target) - elif gate.name in {"RX", "RY", "RZ"}: - (GATE_QISKIT[gate.name])(target_circuit, gate.parameter, gate.target) - elif gate.name in {"CNOT"}: - (GATE_QISKIT[gate.name])(target_circuit, gate.control, gate.target) + (GATE_QISKIT[gate.name])(target_circuit, gate.target[0]) + elif gate.name in {"RX", "RY", "RZ", "PHASE"}: + (GATE_QISKIT[gate.name])(target_circuit, gate.parameter, gate.target[0]) + elif gate.name in {"CRX", "CRY", "CRZ", "CPHASE"}: + (GATE_QISKIT[gate.name])(target_circuit, gate.parameter, gate.control[0], gate.target[0]) + elif gate.name in {"CNOT", "CH", "CX", "CY", "CZ"}: + (GATE_QISKIT[gate.name])(target_circuit, gate.control[0], gate.target[0]) + elif gate.name in {"SWAP"}: + (GATE_QISKIT[gate.name])(target_circuit, gate.target[0], gate.target[1]) + elif gate.name in {"CSWAP"}: + (GATE_QISKIT[gate.name])(target_circuit, gate.control[0], gate.target[0], gate.target[1]) + elif gate.name in {"XX"}: + (GATE_QISKIT[gate.name])(target_circuit, gate.parameter, gate.target[0], gate.target[1]) elif gate.name in {"MEASURE"}: - (GATE_QISKIT[gate.name])(target_circuit, gate.target, gate.target) + (GATE_QISKIT[gate.name])(target_circuit, gate.target[0], gate.target[0]) else: raise ValueError(f"Gate '{gate.name}' not supported on backend qiskit") return target_circuit diff --git a/tangelo/linq/translator/translate_qulacs.py b/tangelo/linq/translator/translate_qulacs.py index fcb288d21..23ae7dddc 100644 --- a/tangelo/linq/translator/translate_qulacs.py +++ b/tangelo/linq/translator/translate_qulacs.py @@ -21,6 +21,7 @@ - how the order and conventions for some of the inputs to the gate operations may also differ. """ +from numpy import exp, cos, sin def get_qulacs_gates(): @@ -35,12 +36,24 @@ def get_qulacs_gates(): GATE_QULACS["X"] = qulacs.QuantumCircuit.add_X_gate GATE_QULACS["Y"] = qulacs.QuantumCircuit.add_Y_gate GATE_QULACS["Z"] = qulacs.QuantumCircuit.add_Z_gate + GATE_QULACS["CH"] = qulacs.gate.H + GATE_QULACS["CX"] = qulacs.gate.X + GATE_QULACS["CY"] = qulacs.gate.Y + GATE_QULACS["CZ"] = qulacs.gate.Z GATE_QULACS["S"] = qulacs.QuantumCircuit.add_S_gate GATE_QULACS["T"] = qulacs.QuantumCircuit.add_T_gate GATE_QULACS["RX"] = qulacs.QuantumCircuit.add_RX_gate GATE_QULACS["RY"] = qulacs.QuantumCircuit.add_RY_gate GATE_QULACS["RZ"] = qulacs.QuantumCircuit.add_RZ_gate GATE_QULACS["CNOT"] = qulacs.QuantumCircuit.add_CNOT_gate + GATE_QULACS["CRX"] = qulacs.gate.RX + GATE_QULACS["CRY"] = qulacs.gate.RY + GATE_QULACS["CRZ"] = qulacs.gate.RZ + GATE_QULACS["PHASE"] = qulacs.gate.DenseMatrix + GATE_QULACS["CPHASE"] = qulacs.gate.DenseMatrix + GATE_QULACS["XX"] = qulacs.gate.DenseMatrix + GATE_QULACS["SWAP"] = qulacs.QuantumCircuit.add_SWAP_gate + GATE_QULACS["CSWAP"] = qulacs.gate.SWAP GATE_QULACS["MEASURE"] = qulacs.gate.Measurement return GATE_QULACS @@ -69,13 +82,46 @@ def translate_qulacs(source_circuit, noise_model=None): # Maps the gate information properly. Different for each backend (order, values) for gate in source_circuit._gates: if gate.name in {"H", "X", "Y", "Z", "S", "T"}: - (GATE_QULACS[gate.name])(target_circuit, gate.target) + (GATE_QULACS[gate.name])(target_circuit, gate.target[0]) + elif gate.name in {"CH", "CX", "CY", "CZ"}: + mat_gate = qulacs.gate.to_matrix_gate(GATE_QULACS[gate.name](gate.target[0])) + for c in gate.control: + mat_gate.add_control_qubit(c, 1) + target_circuit.add_gate(mat_gate) elif gate.name in {"RX", "RY", "RZ"}: - (GATE_QULACS[gate.name])(target_circuit, gate.target, -1. * gate.parameter) + (GATE_QULACS[gate.name])(target_circuit, gate.target[0], -1. * gate.parameter) + elif gate.name in {"CRX", "CRY", "CRZ"}: + mat_gate = qulacs.gate.to_matrix_gate(GATE_QULACS[gate.name](gate.target[0], -1. * gate.parameter)) + for c in gate.control: + mat_gate.add_control_qubit(c, 1) + target_circuit.add_gate(mat_gate) + elif gate.name in {"SWAP"}: + (GATE_QULACS[gate.name])(target_circuit, gate.target[0], gate.target[1]) + elif gate.name in {"CSWAP"}: + mat_gate = qulacs.gate.to_matrix_gate(GATE_QULACS[gate.name](gate.target[0], gate.target[1])) + for c in gate.control: + mat_gate.add_control_qubit(c, 1) + target_circuit.add_gate(mat_gate) + elif gate.name in {"PHASE"}: + mat_gate = GATE_QULACS[gate.name](gate.target[0], [[1, 0], [0, exp(1j * gate.parameter)]]) + target_circuit.add_gate(mat_gate) + elif gate.name in {"CPHASE"}: + mat_gate = GATE_QULACS[gate.name](gate.target[0], [[1, 0], [0, exp(1j * gate.parameter)]]) + for c in gate.control: + mat_gate.add_control_qubit(c, 1) + target_circuit.add_gate(mat_gate) + elif gate.name in {"XX"}: + c = cos(gate.parameter/2) + s = -1j * sin(gate.parameter/2) + mat_gate = GATE_QULACS[gate.name]([gate.target[0], gate.target[1]], [[c, 0, 0, s], + [0, c, s, 0], + [0, s, c, 0], + [s, 0, 0, c]]) + target_circuit.add_gate(mat_gate) elif gate.name in {"CNOT"}: - (GATE_QULACS[gate.name])(target_circuit, gate.control, gate.target) + (GATE_QULACS[gate.name])(target_circuit, gate.control[0], gate.target[0]) elif gate.name in {"MEASURE"}: - gate = (GATE_QULACS[gate.name])(gate.target, gate.target) + gate = (GATE_QULACS[gate.name])(gate.target[0], gate.target[0]) target_circuit.add_gate(gate) else: raise ValueError(f"Gate '{gate.name}' not supported on backend qulacs") @@ -84,13 +130,21 @@ def translate_qulacs(source_circuit, noise_model=None): if noise_model and (gate.name in noise_model.noisy_gates): for nt, np in noise_model._quantum_errors[gate.name]: if nt == 'pauli': - target_circuit.add_gate(Probabilistic(np, [X(gate.target), Y(gate.target), Z(gate.target)])) - if gate.control or gate.control == 0: - target_circuit.add_gate(Probabilistic(np, [X(gate.control), Y(gate.control), Z(gate.control)])) + for t in gate.target: + target_circuit.add_gate(Probabilistic(np, [X(t), Y(t), Z(t)])) + if gate.control is not None: + for c in gate.control: + target_circuit.add_gate(Probabilistic(np, [X(c), Y(c), Z(c)])) elif nt == 'depol': - if gate.control or gate.control == 0: - target_circuit.add_gate(TwoQubitDepolarizingNoise(gate.control, gate.target, (15/16)*np)) + depol_list = [t for t in gate.target] + if gate.control is not None: + depol_list += [c for c in gate.control] + n_depol = len(depol_list) + if n_depol == 2: + target_circuit.add_gate(TwoQubitDepolarizingNoise(*depol_list, (15/16)*np)) + elif n_depol == 1: + target_circuit.add_gate(DepolarizingNoise(depol_list[0], (3/4) * np)) else: - target_circuit.add_gate(DepolarizingNoise(gate.target, (3/4) * np)) + raise ValueError(f'{gate.name} has more than 2 qubits, Qulacs DepolarizingNoise only supports 1- and 2-qubits') return target_circuit From dfac03778b239033272c0d2fdca4b86da71d714d Mon Sep 17 00:00:00 2001 From: MPCoons <84400409+MPCoons@users.noreply.github.com> Date: Wed, 8 Dec 2021 10:24:10 -0500 Subject: [PATCH 20/68] Add QMF and QCC capabilities and tests; small fix to scbk transform in statvector_mapping.py when n_spinorbitals == 4. (#91) * Adding QMF and QCC ansatz to VQE and tests. Co-authored-by: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> --- .../variational/tests/test_vqe_solver.py | 78 +++++ tangelo/algorithms/variational/vqe_solver.py | 15 + .../toolboxes/ansatz_generator/_qubit_cc.py | 163 ++++++++++ .../toolboxes/ansatz_generator/_qubit_mf.py | 195 ++++++++++++ tangelo/toolboxes/ansatz_generator/hea.py | 9 +- tangelo/toolboxes/ansatz_generator/qcc.py | 291 ++++++++++++++++++ tangelo/toolboxes/ansatz_generator/qmf.py | 204 ++++++++++++ tangelo/toolboxes/ansatz_generator/rucc.py | 9 +- .../ansatz_generator/tests/test_qcc.py | 215 +++++++++++++ .../ansatz_generator/tests/test_qmf.py | 141 +++++++++ tangelo/toolboxes/ansatz_generator/uccsd.py | 9 +- tangelo/toolboxes/ansatz_generator/upccgsd.py | 9 +- .../ansatz_generator/variational_circuit.py | 10 +- 13 files changed, 1322 insertions(+), 26 deletions(-) create mode 100644 tangelo/toolboxes/ansatz_generator/_qubit_cc.py create mode 100644 tangelo/toolboxes/ansatz_generator/_qubit_mf.py create mode 100755 tangelo/toolboxes/ansatz_generator/qcc.py create mode 100755 tangelo/toolboxes/ansatz_generator/qmf.py create mode 100644 tangelo/toolboxes/ansatz_generator/tests/test_qcc.py create mode 100644 tangelo/toolboxes/ansatz_generator/tests/test_qmf.py diff --git a/tangelo/algorithms/variational/tests/test_vqe_solver.py b/tangelo/algorithms/variational/tests/test_vqe_solver.py index 861952d28..e6cc6d4c2 100644 --- a/tangelo/algorithms/variational/tests/test_vqe_solver.py +++ b/tangelo/algorithms/variational/tests/test_vqe_solver.py @@ -19,6 +19,8 @@ from tangelo.algorithms import BuiltInAnsatze, VQESolver from tangelo.molecule_library import mol_H2_sto3g, mol_H4_sto3g, mol_H4_cation_sto3g, mol_NaH_sto3g from tangelo.toolboxes.ansatz_generator.uccsd import UCCSD +from tangelo.toolboxes.ansatz_generator.qmf import QMF +from tangelo.toolboxes.ansatz_generator.qcc import QCC from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping from tangelo.toolboxes.molecular_computation.rdms import matricize_2rdm @@ -110,6 +112,31 @@ def test_simulate_h2(self): energy = vqe_solver.simulate() self.assertAlmostEqual(energy, -1.137270422018, delta=1e-4) + def test_simulate_qmf_h2(self): + """Run VQE on H2 molecule, with QMF ansatz, JW qubit mapping, initial + parameters, exact simulator. + """ + + vqe_options = {"molecule": mol_H2_sto3g, "ansatz": BuiltInAnsatze.QMF, "qubit_mapping": "jw", + "verbose": True} + vqe_solver = VQESolver(vqe_options) + vqe_solver.build() + + energy = vqe_solver.simulate() + self.assertAlmostEqual(energy, -1.116684, delta=1e-4) + + def test_simulate_qcc_h2(self): + """Run VQE on H2 molecule, with QCC ansatz, JW qubit mapping, initial + parameters, exact simulator. + """ + vqe_options = {"molecule": mol_H2_sto3g, "ansatz": BuiltInAnsatze.QCC, "qubit_mapping": "jw", + "verbose": True} + vqe_solver = VQESolver(vqe_options) + vqe_solver.build() + + energy = vqe_solver.simulate() + self.assertAlmostEqual(energy, -1.137270, delta=1e-4) + def test_simulate_h2_qiskit(self): """Run VQE on H2 molecule, with UCCSD ansatz, JW qubit mapping, initial parameters, exact qiskit simulator. @@ -137,6 +164,31 @@ def test_simulate_h4(self): energy = vqe_solver.simulate() self.assertAlmostEqual(energy, -1.9778312978826869, delta=1e-4) + def test_simulate_qmf_h4(self): + """Run VQE on H4 molecule, with QMF ansatz, JW qubit mapping, initial + parameters, exact simulator. + """ + + vqe_options = {"molecule": mol_H4_sto3g, "ansatz": BuiltInAnsatze.QMF, "qubit_mapping": "jw", + "verbose": True} + vqe_solver = VQESolver(vqe_options) + vqe_solver.build() + + energy = vqe_solver.simulate() + self.assertAlmostEqual(energy, -1.789483, delta=1e-4) + + def test_simulate_qcc_h4(self): + """Run VQE on H4 molecule, with QCC ansatz, JW qubit mapping, initial + parameters, exact simulator. + """ + vqe_options = {"molecule": mol_H4_sto3g, "ansatz": BuiltInAnsatze.QCC, "qubit_mapping": "jw", + "verbose": True} + vqe_solver = VQESolver(vqe_options) + vqe_solver.build() + + energy = vqe_solver.simulate() + self.assertAlmostEqual(energy, -1.963270, delta=1e-4) + def test_simulate_h4_open(self): """Run VQE on H4 molecule, with UCCSD ansatz, JW qubit mapping, initial parameters, exact simulator """ vqe_options = {"molecule": mol_H4_cation_sto3g, "ansatz": BuiltInAnsatze.UCCSD, "qubit_mapping": "jw", @@ -147,6 +199,32 @@ def test_simulate_h4_open(self): energy = vqe_solver.simulate() self.assertAlmostEqual(energy, -1.6394, delta=1e-3) + def test_simulate_qmf_h4_open(self): + """Run VQE on H4 + molecule, with QMF ansatz, JW qubit mapping, initial + parameters, exact simulator. + """ + + vqe_options = {"molecule": mol_H4_cation_sto3g, "ansatz": BuiltInAnsatze.QMF, "qubit_mapping": "jw", + "verbose": True} + vqe_solver = VQESolver(vqe_options) + vqe_solver.build() + + energy = vqe_solver.simulate() + self.assertAlmostEqual(energy, -1.585918, delta=1e-4) + + def test_simulate_qcc_h4_open(self): + """Run VQE on H4 + molecule, with QCC ansatz, JW qubit mapping, initial + parameters, exact simulator. + """ + + vqe_options = {"molecule": mol_H4_cation_sto3g, "ansatz": BuiltInAnsatze.QCC, "qubit_mapping": "jw", + "verbose": True} + vqe_solver = VQESolver(vqe_options) + vqe_solver.build() + + energy = vqe_solver.simulate() + self.assertAlmostEqual(energy, -1.638020, delta=1e-4) + def test_optimal_circuit_h4(self): """Run VQE on H4 molecule, save optimal circuit. Verify it yields optimal energy. diff --git a/tangelo/algorithms/variational/vqe_solver.py b/tangelo/algorithms/variational/vqe_solver.py index a1db930bc..3b6813218 100644 --- a/tangelo/algorithms/variational/vqe_solver.py +++ b/tangelo/algorithms/variational/vqe_solver.py @@ -33,6 +33,8 @@ from tangelo.toolboxes.ansatz_generator.rucc import RUCC from tangelo.toolboxes.ansatz_generator.hea import HEA from tangelo.toolboxes.ansatz_generator.upccgsd import UpCCGSD +from tangelo.toolboxes.ansatz_generator.qmf import QMF +from tangelo.toolboxes.ansatz_generator.qcc import QCC from tangelo.toolboxes.ansatz_generator.variational_circuit import VariationalCircuitAnsatz from tangelo.toolboxes.ansatz_generator.penalty_terms import combined_penalty from tangelo.toolboxes.post_processing.bootstrapping import get_resampled_frequencies @@ -46,6 +48,8 @@ class BuiltInAnsatze(Enum): UCC3 = 2 HEA = 3 UpCCGSD = 4 + QMF = 5 + QCC = 6 class VQESolver: @@ -106,6 +110,13 @@ def __init__(self, opt_dict): else: raise KeyError(f"Keyword :: {k}, not available in VQESolver") + # The QCC ansatz requires up_then_down=True when mapping="jw" + if self.ansatz == BuiltInAnsatze.QCC and self.qubit_mapping.lower() == "jw" and not self.up_then_down: + warn_msg = "The QCC ansatz requires spin-orbital ordering to be all spin-up "\ + "first followed by all spin-down for the JW mapping." + warnings.warn(warn_msg, RuntimeWarning) + self.up_then_down = True + # Raise error/warnings if input is not as expected. Only a single input # must be provided to avoid conflicts. if not (bool(self.molecule) ^ bool(self.qubit_hamiltonian)): @@ -171,6 +182,10 @@ def build(self): self.ansatz = HEA(self.molecule, self.qubit_mapping, self.up_then_down, **self.ansatz_options) elif self.ansatz == BuiltInAnsatze.UpCCGSD: self.ansatz = UpCCGSD(self.molecule, self.qubit_mapping, self.up_then_down, **self.ansatz_options) + elif self.ansatz == BuiltInAnsatze.QMF: + self.ansatz = QMF(self.molecule, self.qubit_mapping, self.up_then_down, **self.ansatz_options) + elif self.ansatz == BuiltInAnsatze.QCC: + self.ansatz = QCC(self.molecule, self.qubit_mapping, self.up_then_down, **self.ansatz_options) else: raise ValueError(f"Unsupported ansatz. Built-in ansatze:\n\t{self.builtin_ansatze}") elif not isinstance(self.ansatz, Ansatz): diff --git a/tangelo/toolboxes/ansatz_generator/_qubit_cc.py b/tangelo/toolboxes/ansatz_generator/_qubit_cc.py new file mode 100644 index 000000000..aeeee9319 --- /dev/null +++ b/tangelo/toolboxes/ansatz_generator/_qubit_cc.py @@ -0,0 +1,163 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This module implements functions to create the direct interaction set (DIS) +of generators for the qubit coupled cluster (QCC) ansatz and is based on Ref. 1. +The DIS consists of generator groups that are characterized by the magnitude of +the gradient of the QCC energy functional with respect to a variational parameter +tau, |dEQCC/dtau|. Nonzero values of |dEQCC/dtau| imply that an individual +generator will contribute to variational energy lowering. The number of DIS groups +cannot exceed the number of Hamiltonian terms, N, and each DIS group contains +2^nq - 1 generators, where nq is the number of qubits, all with identical values +of |dEQCC/dtau|. By constructing the DIS, it is possible to identify O(N * (2^nq - 1)) +generators that are strong energy-lowering candidates for the QCC ansatz at a +cost of O(N) gradient evaluations. In contrast, a brute-force strategy requires +O(4^nq) gradient evaluations. + +Refs: + 1. I. G. Ryabinkin, R. A. Lang, S. N. Genin, and A. F. Izmaylov. + J. Chem. Theory Comput. 2020, 16, 2, 1055–1063. +""" + +from itertools import combinations + +from tangelo.toolboxes.operators.operators import QubitOperator +from ._qubit_mf import get_op_expval + + +def construct_dis(pure_var_params, qubit_ham, qcc_deriv_thresh, verbose=False): + """Construct the DIS of QCC generators, which proceeds as follows: + 1. Identify the flip indices of all Hamiltonian terms and group terms by flip indices. + 2. Construct a representative generator using flip indices from each candidate DIS group + and evaluate dEQCC/dtau for all Hamiltonian terms. + 3. For each candidate DIS group, sum dEQCC/dtau for all Hamiltonian terms. + If |dEQCC/dtau| >= thresh add the candidate DIS group to the DIS. + 4. For all DIS groups, create a complete set of generators made from Pauli X and and an + odd number of Y operators. + + Args: + pure_var_params (numpy array of float): A purified QMF variational parameter set. + qubit_ham (QubitOperator): A qubit Hamiltonian. + qcc_deriv_thresh (float): Threshold value of |dEQCC/dtau| so that if |dEQCC/dtau| >= + qcc_deriv_thresh for a generator, add its candidate group to the DIS. + verbose (bool): Flag for QCC verbosity. + + Returns: + list of list: the DIS of QCC generators. + """ + + # Use a qubit Hamiltonian and purified QMF parameter set to construct the DIS + dis, dis_groups = [], get_dis_groups(pure_var_params, qubit_ham, qcc_deriv_thresh) + if dis_groups: + if verbose: + print(f"The DIS contains {len(dis_groups)} unique generator group(s).\n") + for i, dis_group in enumerate(dis_groups): + dis_group_idxs = [int(idxs) for idxs in dis_group[0].split(" ")] + dis_group_gens = get_gens_from_idxs(dis_group_idxs) + dis.append(dis_group_gens) + if verbose: + print(f"DIS group {i} | group size = {len(dis_group_gens)} | "\ + f"flip indices = {dis_group_idxs} | |dEQCC/dtau| = "\ + f"{abs(dis_group[1])} a.u.\n") + else: + raise ValueError(f"The DIS is empty: there are no candidate DIS groups where "\ + f"|dEQCC/dtau| >= {qcc_deriv_thresh} a.u. Terminate the QCC simulation.\n") + return dis + + +def get_dis_groups(pure_var_params, qubit_ham, qcc_deriv_thresh): + """Construct unique DIS groups characterized by the flip indices and |dEQCC/dtau|. + + Args: + pure_var_params (numpy array of float): A purified QMF variational parameter set. + qubit_ham (QubitOperator): A qubit Hamiltonian. + qcc_deriv_thresh (float): Threshold value of |dEQCC/dtau| so that if |dEQCC/dtau| >= + qcc_deriv_thresh for a generator, add its candidate group to the DIS. + + Returns: + list of tuple: the DIS group flip indices (str) and signed value of dEQCC/dtau (float). + """ + + # Get the flip indices from qubit_ham and compute the gradient dEQCC/dtau + qham_gen = ((qham_items[0], (qham_items[1], pure_var_params))\ + for qham_items in qubit_ham.terms.items()) + flip_idxs = list(filter(None, (get_idxs_deriv(q_gen[0], *q_gen[1]) for q_gen in qham_gen))) + + # Group Hamiltonian terms with the same flip indices and sum signed dEQCC/tau values + candidates = dict() + for idxs in flip_idxs: + deriv_old = candidates.get(idxs[0], 0.) + candidates[idxs[0]] = idxs[1] + deriv_old + + # Return a sorted list of flip indices and signed dEQCC/dtau values for each DIS group + dis_groups = [idxs_deriv for idxs_deriv in candidates.items()\ + if abs(idxs_deriv[1]) >= qcc_deriv_thresh] + return sorted(dis_groups, key=lambda deriv: abs(deriv[1]), reverse=True) + + +def get_idxs_deriv(qham_term, *qham_qmf_data): + """Find the flip indices of a qubit Hamiltonian term by identifying the indices + of any X and Y operators that are present. A representative generator is then + built with a Pauli Y operator acting on the first flip index and then Pauli X + operators acting on the remaining flip indices. Then dEQCC/dtau is evaluated as + dEQCC_dtau = -i/2 = -i . + + Args: + qham_term (tuple of tuple): The Pauli operators and indices of a QubitOperator term. + qham_qmf_data (tuple): The coefficient of a QubitOperator term and a purified QMF + variational parameter set (numpy array of float). + + Returns: + tuple or None: return a tuple of the flip indices (str) and the signed value of + dEQCC/dtau (float) if at least two flip indices were found. Otherwise return None. + """ + + coef, pure_params = qham_qmf_data + idxs, gen_list, idxs_deriv = "", [], None + for pauli_factor in qham_term: + # The indices of X and Y operators are flip indices + idx, pauli_op = pauli_factor + if "X" in pauli_op or "Y" in pauli_op: + gen = (idx, "Y") if idxs == "" else (idx, "X") + idxs = idxs + f" {idx}" if idxs != "" else f"{idx}" + gen_list.append(gen) + # Generators must have at least two flip indices + if len(gen_list) > 1: + qham_gen_comm = QubitOperator(qham_term, -1j * coef) + qham_gen_comm *= QubitOperator(tuple(gen_list), 1.) + deriv = get_op_expval(qham_gen_comm, pure_params).real + idxs_deriv = (idxs, deriv) + return idxs_deriv + + +def get_gens_from_idxs(group_idxs): + """Given the flip indices of a DIS group, create all possible Pauli words made + from Pauli X and an odd number of Y operators acting on qubits indexed by the + flip indices. + + Args: + group_idxs (str): A set of flip indices for a DIS group. + + Returns: + list of QubitOperator: DIS group generators. + """ + + dis_group_gens = [] + for n_y in range(1, len(group_idxs), 2): + # Create combinations of odd numbers of flip indices for the Pauli Y operators + for xy_idx in combinations(group_idxs, n_y): + # If a flip index idx matches xy_idx, add a Y operator + gen_list = [(idx, "Y") if idx in xy_idx else (idx, "X") for idx in group_idxs] + dis_group_gens.append(QubitOperator(tuple(gen_list), 1.)) + return dis_group_gens diff --git a/tangelo/toolboxes/ansatz_generator/_qubit_mf.py b/tangelo/toolboxes/ansatz_generator/_qubit_mf.py new file mode 100644 index 000000000..79ae9923e --- /dev/null +++ b/tangelo/toolboxes/ansatz_generator/_qubit_mf.py @@ -0,0 +1,195 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This module implements a collection of functions related to the QMF +ansatz: (1) analytically evaluate an expectation value of a QubitOperator +using a QMF wave function; (2) initialize the QMF variational parameter set +{Omega} from a Hartree-Fock reference state; (3) purify {Omega} when building +and screening the DIS of QCC generators; (4) build a QMF state preparation +circuit using {Omega}; (5) create penalty terms for N, S^2, and Sz to penalize +a mean-field Hamiltonian. For more information, see references below. + +Refs: + 1. I. G. Ryabinkin and S. N. Genin. + https://arxiv.org/abs/1812.09812 2018. + 2. S. N. Genin, I. G. Ryabinkin, and A. F. Izmaylov. + https://arxiv.org/abs/1901.04715 2019. + 3. I. G. Ryabinkin, S. N. Genin, and A. F. Izmaylov. + J. Chem. Theory Comput. 2019, 15, 1, 249–255. +""" + +import numpy as np + +from tangelo.linq import Circuit, Gate +from tangelo.toolboxes.operators.operators import FermionOperator +from tangelo.toolboxes.qubit_mappings.statevector_mapping import get_vector +from .penalty_terms import combined_penalty, number_operator_penalty, spin2_operator_penalty,\ + spin_operator_penalty + + +def get_op_expval(qubit_op, qmf_var_params): + """Driver function for analytical evaluation of a QubitOperator expectation value with a + QMF wave function. + + Args: + qubit_op (QubitOperator): A qubit operator to compute the expectation value of. + qmf_var_params (numpy array of float): The QMF variational parameter set. + + Returns: + complex: expectation value of all qubit operator terms. + """ + + n_qubits = qmf_var_params.size // 2 + qubit_op_gen = ((qop_items[0], (qop_items[1], qmf_var_params, n_qubits))\ + for qop_items in qubit_op.terms.items()) + return sum([calc_op_expval(qop_gen[0], *qop_gen[1]) for qop_gen in qubit_op_gen]) + + +def calc_op_expval(qop_term, *qop_qmf_data): + """Analytically evaluate a qubit operator expectation value with a QMF wave function. + The expectation values of Pauli operators X, Y, and Z are (Ref. 2) + = cos(phi) * sin(theta), = sin(phi) * sin(theta), + = cos(theta) + + Args: + qop_term (tuple of tuple): The Pauli operators and indices of a QubitOperator term. + qop_qmf_data (tuple): The coefficient of a QubitOperator term, QMF variational parameter + set (numpy array of float), and number of qubits (int). + + Returns: + complex: expectation value of a qubit operator term. + """ + + coef, qmf_var_params, n_qubits = qop_qmf_data + for idx, pauli in qop_term: + theta, phi = qmf_var_params[idx], qmf_var_params[idx + n_qubits] + if pauli == "X": + coef *= np.cos(phi) * np.sin(theta) + elif pauli == "Y": + coef *= np.sin(phi) * np.sin(theta) + elif pauli == "Z": + coef *= np.cos(theta) + return coef + + +def init_qmf_from_hf(n_spinorbitals, n_electrons, mapping, up_then_down=False, spin=None): + """Function to initialize the QMF variational parameter set from a Hartree-Fock state + occupation vector. The theta Bloch angles are set to 0. or np.pi if the molecular orbital is + unoccupied or occupied, respectively. The phi Bloch angles are set to 0. + + Args: + n_spinorbitals (int): Number of spin-orbitals in the molecular system. + n_electrons (int): Number of electrons in the molecular system. + mapping (str) : One of the supported qubit mapping identifiers. + up_then_down (bool): Change basis ordering putting all spin-up orbitals first, + followed by all spin-down. + spin (int): 2*S = n_alpha - n_beta. + + Returns: + numpy array of float: QMF variational parameter set. + """ + + # Get thetas from HF vec and arrange Bloch angles so all thetas are first then phis + thetas = get_vector(n_spinorbitals, n_electrons, mapping, up_then_down, spin) + return np.concatenate((np.pi * thetas, np.zeros((len(thetas),), dtype=float))) + + +def purify_qmf_state(qmf_var_params, n_spinorbitals, n_electrons, mapping, up_then_down=False,\ + spin=None, verbose=False): + """The efficient construction and screening of the DIS requires a z-collinear QMF state. + If the QMF state specified by qmf_var_params is not z-collinear, this function adjusts the + parameters to the nearest z-collinear computational basis state. + + Args: + qmf_var_params (numpy array of float): QMF variational parameter set. + n_spinorbitals (int): Number of spin-orbitals in the molecular system. + n_electrons (int): Number of electrons in the molecular system. + mapping (str) : One of the supported qubit mapping identifiers. + up_then_down (bool): Change basis ordering putting all spin-up orbitals first, + followed by all spin-down. + spin (int): 2*S = n_alpha - n_beta. + verbose (bool): Flag for QMF verbosity. + + Returns: + numpy array of float: purified QMF parameter set that corresponds to the + nearest z-collinear state to the current QMF state. + """ + + # Adjust the theta Bloch angles + pure_var_params, n_qubits = np.copy(qmf_var_params), qmf_var_params.size // 2 + for i, theta in enumerate(qmf_var_params[:n_qubits]): + c_0, c_1 = np.cos(0.5 * theta), np.sin(0.5 * theta) + if abs(c_0) > abs(c_1): + pure_var_params[i] = 0. + elif abs(c_0) < abs(c_1): + pure_var_params[i] = np.pi + else: + vector = get_vector(n_spinorbitals, n_electrons, mapping, up_then_down, spin) + pure_var_params[i] = np.pi * vector[i] + if verbose: + print(f"Purified QMF_{i} Bloch angles: (theta, phi) = ({pure_var_params[i]}, {pure_var_params[i + n_qubits]})\n") + return pure_var_params + + +def get_qmf_circuit(qmf_var_params, variational=True): + """Build a QMF state preparation circuit using the current state of the QMF variational + parameter set {Omega}. The first n_qubit elements in {Omega} are parameters for RX gates, + and the second n_qubit elements in {Omega} are parameters for RZ gates. + + Args: + qmf_var_params (numpy array of float): The QMF variational parameter set. + variational (bool): Flag to treat {Omega} variationally or not. + + Returns: + Circuit: instance of tangelo.linq Circuit class. + """ + + n_qubits, gates = qmf_var_params.size // 2, [] + for idx, param in enumerate(qmf_var_params): + gate_id = "RX" if idx < n_qubits else "RZ" + gates.append(Gate(gate_id, target=idx, parameter=param, is_variational=variational)) + return Circuit(gates) + + +def penalize_mf_ham(mf_pen_terms, n_orbitals): + """Generate a FermionOperator that is used to penalize a mean-field Hamiltonian for at + least one of the N, S^2, or Sz operators. + + Args: + mf_pen_terms (dict): Parameters for mean-field Hamiltonian penalization. + The keys are "N", "S^2", or "Sz" (str) and the values are a tuple of the penalty + term coefficient, mu (float), and the target value of a penalty operator (int). + Example - "key": (mu, target). Key, value pairs are case sensitive and mu > 0. + n_orbitals (int): Number of orbitals in the fermion basis. + + Returns: + FermionOperator: sum of all mean-field Hamiltonian penalty terms. + """ + + mf_penalty = FermionOperator.zero() + penalty_terms = list(key for key in mf_pen_terms.keys()) + if len(penalty_terms) == 3: + mf_penalty += combined_penalty(n_orbitals, mf_pen_terms, False) + else: + for penalty_term in penalty_terms: + coef, target = mf_pen_terms[penalty_term] + if coef <= 0.: + raise ValueError("The penalty term coefficient must be positive.") + if penalty_term == "N": + mf_penalty += number_operator_penalty(n_orbitals, target, coef, False) + elif penalty_term == "S^2": + mf_penalty += spin2_operator_penalty(n_orbitals, target, coef, False) + elif penalty_term == "Sz": + mf_penalty += spin_operator_penalty(n_orbitals, target, coef, False) + return mf_penalty diff --git a/tangelo/toolboxes/ansatz_generator/hea.py b/tangelo/toolboxes/ansatz_generator/hea.py index 6d3c75987..c5fb4f2b3 100644 --- a/tangelo/toolboxes/ansatz_generator/hea.py +++ b/tangelo/toolboxes/ansatz_generator/hea.py @@ -111,11 +111,10 @@ def set_var_params(self, var_params=None): elif var_params == "zeros": initial_var_params = np.zeros((self.n_var_params,), dtype=float) else: - try: - assert (len(var_params) == self.n_var_params) - initial_var_params = np.array(var_params) - except AssertionError: - raise ValueError(f"Expected {self.n_var_params} variational parameters but received {len(var_params)}.") + initial_var_params = np.array(var_params) + if initial_var_params.size != self.n_var_params: + raise ValueError(f"Expected {self.n_var_params} variational parameters but "\ + f"received {initial_var_params.size}.") self.var_params = initial_var_params return initial_var_params diff --git a/tangelo/toolboxes/ansatz_generator/qcc.py b/tangelo/toolboxes/ansatz_generator/qcc.py new file mode 100755 index 000000000..47b1323f6 --- /dev/null +++ b/tangelo/toolboxes/ansatz_generator/qcc.py @@ -0,0 +1,291 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This module defines the qubit coupled cluster (QCC) ansatz class. The +motivation behind this ansatz is to provide an improved alternative to +classical unitary coupled cluster for describing the electron correlation of +molecular systems. This implementation is based on Ref. 1, where the ansaztz +takes the form of a variational product state built directly from a set of +parameterized exponentiated qubit operators. A qubit operator is selected for +the ansatz based on an energy gradient criterion that indicates its potential +contribution to variational lowering of the QCC energy. For chemical applications, +the quantum mean-field ansatz is used in conjunction with this ansatz to +describe an electronic wave function on a quantum computer. For more information +about this ansatz and its variations, see references below. + +Refs: + 1. I. G. Ryabinkin, T.-C. Yen, S. N. Genin, and A. F. Izmaylov. + J. Chem. Theory Comput. 2018, 14 (12), 6317-6326. + 2. I. G. Ryabinkin, R. A. Lang, S. N. Genin, and A. F. Izmaylov. + J. Chem. Theory Comput. 2020, 16, 2, 1055–1063. + 3. R. A. Lang, I. G. Ryabinkin, and A. F. Izmaylov. + J. Chem. Theory Comput. 2021, 17, 1, 66–78. +""" + +import warnings +import numpy as np + +from tangelo.toolboxes.operators.operators import QubitOperator +from tangelo.toolboxes.qubit_mappings.mapping_transform import get_qubit_number,\ + fermion_to_qubit_mapping +from tangelo.linq import Circuit +from .ansatz import Ansatz +from .ansatz_utils import pauliword_to_circuit +from ._qubit_mf import init_qmf_from_hf, get_qmf_circuit, purify_qmf_state +from ._qubit_cc import construct_dis + + +class QCC(Ansatz): + """This class implements the QCC ansatz. Closed-shell and restricted open-shell QCC are + supported. While the form of the QCC ansatz is the same for either variation, the underlying + fermionic mean-field state is treated differently depending on the spin. Closed-shell + or restricted open-shell QCC implies that spin = 0 or spin != 0 and the fermionic mean-field + state is obtained using a RHF or ROHF Hamiltonian, respectively. + + Args: + molecule (SecondQuantizedMolecule): The molecular system. + mapping (str): One of the supported qubit mapping identifiers. Default, "JW". + up_then_down (bool): Change basis ordering putting all spin up orbitals first, + followed by all spin down. Default, False. + qubit_op_list (list of QubitOperator): A list of QCC generators to use for the ansatz. + Default, None. + qmf_circuit (Circuit): An instance of tangelo.linq Circuit class implementing a QMF state + preparation circuit. A variational circuit can be passed from the QMF ansatz class. + Otherwise a non-variational circuit is created by default. Default, None. + qmf_var_params (list or numpy array of float): The QMF variational parameter set. + If None, the values are determined using a Hartree-Fock reference state. Default, None. + qubit_mf_ham (QubitOperator): Allows a qubit Hamiltonian to be passed to the QCC ansatz + class. If not None, then the fermionic Hamiltonian is ignored. Default, None. + qcc_guess (float): Sets the initial guess for all amplitudes in the QCC variational + parameter set. Default, 1.e-1 a.u. + qcc_deriv_thresh (float): Threshold value of |dEQCC/dtau| so that if |dEQCC/dtau| >= + qcc_deriv_thresh for a generator, add its candidate group to the DIS. + max_qcc_gens (int or None): Maximum number of generators allowed for the ansatz. + If None, one generator from each DIS group is used. If set to an int, then + min(size(DIS), max_qcc_gens) generators are used for the ansatz. Default, None. + verbose (bool): Flag for QCC verbosity. Default, False. + """ + + def __init__(self, molecule, mapping="JW", up_then_down=False, qubit_op_list=None,\ + qmf_circuit=None, qmf_var_params=None, qubit_mf_ham=None, qcc_guess=1.e-1,\ + qcc_deriv_thresh=1.e-3, max_qcc_gens=None, verbose=False): + + self.molecule = molecule + self.n_spinorbitals = self.molecule.n_active_sos + if self.n_spinorbitals % 2 != 0: + raise ValueError("The total number of spin-orbitals should be even.") + + self.n_electrons = self.molecule.n_active_electrons + self.spin = molecule.spin + self.mapping = mapping + self.n_qubits = get_qubit_number(self.mapping, self.n_spinorbitals) + self.up_then_down = up_then_down + if self.mapping.upper() == "JW" and not self.up_then_down: + warnings.warn("The QCC ansatz requires spin-orbital ordering to be all spin-up "\ + "first followed by all spin-down for the JW mapping.", RuntimeWarning) + self.up_then_down = True + + self.qcc_guess = qcc_guess + self.qcc_deriv_thresh = qcc_deriv_thresh + self.max_qcc_gens = max_qcc_gens + self.qubit_op_list = qubit_op_list + self.qmf_var_params = qmf_var_params + self.qmf_circuit = qmf_circuit + self.verbose = verbose + + if qubit_mf_ham is None: + self.fermi_ham = self.molecule.fermionic_hamiltonian + self.qubit_ham = fermion_to_qubit_mapping(self.fermi_ham, self.mapping,\ + self.n_spinorbitals, self.n_electrons,\ + self.up_then_down, self.spin) + else: + self.qubit_ham = qubit_mf_ham + + if self.qmf_var_params is None: + self.qmf_var_params = init_qmf_from_hf(self.n_spinorbitals, self.n_electrons,\ + self.mapping, self.up_then_down, self.spin) + elif isinstance(self.qmf_var_params, list): + self.qmf_var_params = np.array(self.qmf_var_params) + if self.qmf_var_params.size != 2 * self.n_qubits: + raise ValueError("The number of QMF variational parameters must be 2 * n_qubits.") + + # Get purified QMF parameters and use them to build the DIS or use a list of generators. + if self.qubit_op_list is None: + pure_var_params = purify_qmf_state(self.qmf_var_params, self.n_spinorbitals,\ + self.n_electrons, self.mapping, self.up_then_down,\ + self.spin, self.verbose) + self.dis = construct_dis(pure_var_params, self.qubit_ham, self.qcc_deriv_thresh,\ + self.verbose) + self.n_var_params = len(self.dis) if self.max_qcc_gens is None\ + else min(len(self.dis), self.max_qcc_gens) + else: + self.dis = None + self.n_var_params = len(self.qubit_op_list) + + # Supported reference state initialization + self.supported_reference_state = {"HF"} + # Supported var param initialization + self.supported_initial_var_params = {"zeros", "qcc_guess"} + + # Default starting parameters for initialization + self.pauli_to_angles_mapping = {} + self.default_reference_state = "HF" + self.var_params_default = "qcc_guess" + self.var_params = None + self.rebuild_dis = False + self.qcc_circuit = None + self.circuit = None + + def set_var_params(self, var_params=None): + """Set values for variational parameters, such as zeros or floats, + providing some keywords for users, and also supporting direct user input + (list or numpy array). Return the parameters so that workflows such as VQE can + retrieve these values. """ + + if var_params is None: + var_params = self.var_params_default + + if isinstance(var_params, str): + var_params = var_params.lower() + if var_params not in self.supported_initial_var_params: + raise ValueError(f"Supported keywords for initializing variational parameters: "\ + f"{self.supported_initial_var_params}") + # Initialize the QCC wave function as |QCC> = |QMF> + if var_params == "zeros": + initial_var_params = np.zeros((self.n_var_params,), dtype=float) + # Initialize all tau parameters to the same value specified by self.qcc_guess + elif var_params == "qcc_guess": + initial_var_params = self.qcc_guess * np.ones((self.n_var_params,)) + else: + initial_var_params = np.array(var_params) + if initial_var_params.size != self.n_var_params: + raise ValueError(f"Expected {self.n_var_params} variational parameters but "\ + f"received {initial_var_params.size}.") + self.var_params = initial_var_params + return initial_var_params + + def prepare_reference_state(self): + """Returns circuit preparing the reference state of the ansatz (e.g prepare reference + wavefunction with HF, multi-reference state, etc). These preparations must be consistent + with the transform used to obtain the qubit operator. """ + + if self.default_reference_state not in self.supported_reference_state: + raise ValueError(f"Only supported reference state methods are: "\ + f"{self.supported_reference_state}.") + if self.default_reference_state == "HF": + reference_state_circuit = get_qmf_circuit(self.qmf_var_params, False) + return reference_state_circuit + + def build_circuit(self, var_params=None): + """Build and return the quantum circuit implementing the state preparation ansatz + (with currently specified initial_state and var_params). """ + + if var_params is not None: + self.set_var_params(var_params) + elif self.var_params is None: + self.set_var_params() + + # Build a qubit operator required for QCC + qubit_op = self._get_qcc_qubit_op() + + # Build a QMF state preparation circuit + if self.qmf_circuit is None: + self.qmf_circuit = self.prepare_reference_state() + + # Obtain quantum circuit through trivial trotterization of the qubit operator + # Keep track of the order in which pauli words have been visited for fast parameter updates + pauli_words = sorted(qubit_op.terms.items(), key=lambda x: len(x[0])) + pauli_words_gates = [] + for i, (pauli_word, coef) in enumerate(pauli_words): + pauli_words_gates += pauliword_to_circuit(pauli_word, coef) + self.pauli_to_angles_mapping[pauli_word] = i + self.qcc_circuit = Circuit(pauli_words_gates) + self.circuit = self.qmf_circuit + self.qcc_circuit if self.qmf_circuit.size != 0\ + else self.qcc_circuit + + def update_var_params(self, var_params): + """Shortcut: set value of variational parameters in the existing ansatz circuit member. + Preferable to rebuilding your circuit from scratch, which can be an involved process. + """ + + self.set_var_params(var_params) + + # Build the qubit operator required for QCC + qubit_op = self._get_qcc_qubit_op() + + # If qubit_op terms have changed, rebuild circuit. else update variational gates directly + if set(self.pauli_to_angles_mapping.keys()) != set(qubit_op.terms.keys()): + self.build_circuit(var_params) + else: + for pauli_word, coef in qubit_op.terms.items(): + gate_index = self.pauli_to_angles_mapping[pauli_word] + gate_param = 2. * coef if coef >= 0. else 4 * np.pi + 2 * coef + self.qcc_circuit._variational_gates[gate_index].parameter = gate_param + self.circuit = self.qmf_circuit + self.qcc_circuit if self.qmf_circuit.size != 0\ + else self.qcc_circuit + + def _get_qcc_qubit_op(self): + """Returns the QCC operator by selecting one generator from n_var_params DIS groups. + The QCC qubit operator is constructed as a linear combination of generators using the + parameter set {tau} as coefficients: QCC operator = -0.5 * SUM_k P_k * tau_k. + The exponentiated terms of the QCC operator, U = PROD_k exp(-0.5j * tau_k * P_k), + are used to build a QCC circuit. + + Args: + var_params (numpy array of float): The QCC variational parameter set. + n_var_params (int): Size of the QCC variational parameter set. + qmf_var_params (numpy array of float): The QMF variational parameter set. + qubit_ham (QubitOperator): A qubit Hamiltonian. + qcc_deriv_thresh (float): Threshold value of |dEQCC/dtau| so that if |dEQCC/dtau| >= + qcc_deriv_thresh for a generator, add its candidate group to the DIS. + dis (list of list): The DIS of QCC generators. + qubit_op_list (list of QubitOperator): A list of generators to use when building the QCC + operator instead of selecting from DIS groups. + rebuild_dis (bool): Rebuild the DIS. This is useful if qubit_ham of qmf_var_params have + changed (e.g. in iterative methods like iQCC or QCC-ILC). If True, qubit_op_list is + reset to None. + verbose (bool): Flag for QCC verbosity. Default, False. + + Returns: + QubitOperator: QCC ansatz qubit operator. + """ + + # Rebuild the DIS in case qubit_ham changed or both the DIS and qubit_op_list don't exist + if self.rebuild_dis or (self.dis is None and self.qubit_op_list is None): + pure_var_params = purify_qmf_state(self.qmf_var_params, self.n_spinorbitals,\ + self.n_electrons, self.mapping, self.up_then_down,\ + self.spin, self.verbose) + self.dis = construct_dis(pure_var_params, self.qubit_ham, self.qcc_deriv_thresh,\ + self.verbose) + self.n_var_params = len(self.dis) if self.max_qcc_gens is None\ + else min(len(self.dis), self.max_qcc_gens) + self.qubit_op_list = None + + # Build the QCC operator using the DIS or a list of generators + qcc_qubit_op = QubitOperator.zero() + if self.qubit_op_list is None: + self.qubit_op_list = [] + for i in range(self.n_var_params): + # Instead of randomly choosing a generator, get the last one. + qcc_gen = self.dis[i][-1] + qcc_qubit_op -= 0.5 * self.var_params[i] * qcc_gen + self.qubit_op_list.append(qcc_gen) + else: + if len(self.qubit_op_list) == self.n_var_params: + for i, qcc_gen in enumerate(self.qubit_op_list): + qcc_qubit_op -= 0.5 * self.var_params[i] * qcc_gen + else: + raise ValueError(f"Expected {self.n_var_params} generators in "\ + f"{self.qubit_op_list} but received {len(self.qubit_op_list)}.\n") + return qcc_qubit_op diff --git a/tangelo/toolboxes/ansatz_generator/qmf.py b/tangelo/toolboxes/ansatz_generator/qmf.py new file mode 100755 index 000000000..1f6a606a1 --- /dev/null +++ b/tangelo/toolboxes/ansatz_generator/qmf.py @@ -0,0 +1,204 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This module defines the qubit mean-field (QMF) ansatz class. The ansatz is a +variational product state built from a set of parameterized single-qubit states. +For applications in quantum chemistry, the ansatz can be used to describe the +mean-field component of an electronic wave function on a quantum computer. +For more information about this ansatz, see references below. + +Refs: + 1. I. G. Ryabinkin, T.-C. Yen, S. N. Genin, and A. F. Izmaylov. + J. Chem. Theory Comput. 2018, 14 (12), 6317-6326. + 2. I. G. Ryabinkin and S. N. Genin. + https://arxiv.org/abs/1812.09812 2018. + 3. S. N. Genin, I. G. Ryabinkin, and A. F. Izmaylov. + https://arxiv.org/abs/1901.04715 2019. + 4. I. G. Ryabinkin, S. N. Genin, and A. F. Izmaylov. + J. Chem. Theory Comput. 2019, 15, 1, 249–255. +""" + +import warnings +import numpy as np + +from tangelo.toolboxes.qubit_mappings.mapping_transform import get_qubit_number,\ + fermion_to_qubit_mapping +from .ansatz import Ansatz +from ._qubit_mf import get_qmf_circuit, init_qmf_from_hf, penalize_mf_ham + + +class QMF(Ansatz): + """This class implements the QMF ansatz. Closed-shell and restricted open-shell QMF are + supported. While the form of the QMF ansatz is the same for either variation, the underlying + fermionic mean-field state is treated differently depending on the spin. Closed-shell + or restricted open-shell QMF implies that spin = 0 or spin != 0 and the fermionic mean-field + state is obtained using a RHF or ROHF Hamiltonian, respectively. + + Optimizing QMF variational parameters can be risky without taking proper precautions, + especially when a random initial guess is used. It is recommended that penalty terms are + added to the mean-field Hamiltonian to enforce appropriate electron number and spin angular + momentum symmetries on the QMF wave function during optimization (see Ref. 4). If using + penalty terms is to be avoided, an inital guess based on a Hartree-Fock reference state will + likely converge quickly to the desired state, but this is not guaranteed. + + Args: + molecule (SecondQuantizedMolecule): The molecular system. + mapping (str): One of the supported qubit mapping identifiers. Default, "JW". + up_then_down (bool): Change basis ordering putting all spin up orbitals first, + followed by all spin down. Default, False. + init_qmf (dict): Parameters for initializing the QMF variational parameter set and + mean-field Hamiltonian penalization. The keys are "init_params", "N", "S^2", or "Sz" + (str). The value of "init_params" must be in self.supported_initial_var_params (str). + The value of "N", "S^2", or "Sz" is (tuple or None). If a tuple, its elements are + the penalty term coefficient, mu (float), and target value of a penalty operator + (int). Example - "key": (mu, target). If "N", "S^2", or "Sz" is None, a penalty term + is added with default mu and target values: mu = 1.5 and target is derived + from molecule as = n_electrons, = spin_z * (spin_z + 1), and = spin_z, + where spin_z = spin // 2. Key, value pairs are case sensitive and mu > 0. + Default, {"init_params": "hf_state"}. + """ + + def __init__(self, molecule, mapping="JW", up_then_down=False, init_qmf=None): + + self.molecule = molecule + self.n_spinorbitals = self.molecule.n_active_sos + if self.n_spinorbitals % 2 != 0: + raise ValueError("The total number of spin-orbitals should be even.") + + self.n_orbitals = self.n_spinorbitals // 2 + self.n_electrons = self.molecule.n_active_electrons + self.spin = molecule.spin + + self.mapping = mapping + self.n_qubits = get_qubit_number(self.mapping, self.n_spinorbitals) + self.up_then_down = up_then_down + self.init_qmf = {"init_params": "hf_state"} if init_qmf is None else init_qmf + + # Supported var param initialization + self.supported_initial_var_params = {"zeros", "half_pi", "pis", "random", "hf_state"} + + # Supported reference state initialization + self.supported_reference_state = {"HF"} + + # Get the mean-field fermionic Hamiltonian and check for penalty terms + self.fermi_ham = self.molecule.fermionic_hamiltonian + if isinstance(self.init_qmf, dict): + if "init_params" not in self.init_qmf.keys(): + raise KeyError(f"Missing key 'init_params' in {self.init_qmf}. "\ + f"Supported values are {self.supported_initial_var_params}.") + if self.init_qmf["init_params"] in self.supported_initial_var_params: + # Set the default QMF parameter procedure + self.var_params_default = self.init_qmf.pop("init_params") + # Check for at least one penalty term + if self.init_qmf: + # Set default penalty term values + spin_z = self.spin // 2 + init_qmf_defaults = {"N": (1.5, self.n_electrons),\ + "S^2": (1.5, spin_z * (spin_z + 1)), "Sz": (1.5, spin_z)} + # Check if the user requested default values for any penalty terms + for term, params in init_qmf_defaults.items(): + if params is None: + self.init_qmf[term] = init_qmf_defaults[term] + # Add the penalty terms to the mean-field Hamiltonian + self.fermi_ham += penalize_mf_ham(self.init_qmf, self.n_orbitals) + else: + if self.var_params_default != "hf_state": + warnings.warn("It is recommended that the QMF parameters are intialized "\ + "using a Hartree-Fock reference state if penalty terms are "\ + "not added to the mean-field Hamiltonian.", RuntimeWarning) + else: + raise ValueError(f"Unrecognized value for 'init_params' key in {self.init_qmf} "\ + f"Supported values are {self.supported_initial_var_params}.") + else: + raise TypeError(f"{self.init_qmf} must be dictionary type.") + + self.qubit_ham = fermion_to_qubit_mapping(self.fermi_ham, self.mapping, self.n_spinorbitals,\ + self.n_electrons, self.up_then_down, self.spin) + + # Default starting parameters for initialization + self.n_var_params = 2 * self.n_qubits + self.default_reference_state = "HF" + self.var_params = None + self.circuit = None + + def set_var_params(self, var_params=None): + """Set values for variational parameters, such as zeros, random numbers, + or a Hartree-Fock state occupation vector, providing some keywords + for users, and also supporting direct user input (list or numpy array). + Return the parameters so that workflows such as VQE can retrieve these values. """ + + if var_params is None: + var_params = self.var_params_default + + if isinstance(var_params, str): + var_params = var_params.lower() + if var_params not in self.supported_initial_var_params: + raise ValueError(f"Supported keywords for initializing variational parameters: "\ + f"{self.supported_initial_var_params}") + # Initialize |QMF> as |00...0> + if var_params == "zeros": + initial_var_params = np.zeros((self.n_var_params,), dtype=float) + # Initialize |QMF> as (i/sqrt(2))^n_qubits * tensor_prod(|0> + |1>) + elif var_params == "half_pi": + initial_var_params = 0.5 * np.pi * np.ones((self.n_var_params,)) + # Initialize |QMF> as (-1)^n_qubits |11...1> state + elif var_params == "pis": + initial_var_params = np.pi * np.ones((self.n_var_params,)) + # Initialize theta and phi angles randomly over [0, pi] and [0, 2*pi], respectively + elif var_params == "random": + initial_thetas = np.pi * np.random.random((self.n_qubits,)) + initial_phis = 2. * np.pi * np.random.random((self.n_qubits,)) + initial_var_params = np.concatenate((initial_thetas, initial_phis)) + # Initialize theta angles so that |QMF> = |HF> state and set all phi angles to 0. + elif var_params == "hf_state": + initial_var_params = init_qmf_from_hf(self.n_spinorbitals, self.n_electrons,\ + self.mapping, self.up_then_down, self.spin) + else: + initial_var_params = np.array(var_params) + if initial_var_params.size != self.n_var_params: + raise ValueError(f"Expected {self.n_var_params} variational parameters but "\ + f"received {initial_var_params.size}.") + self.var_params = initial_var_params + return initial_var_params + + def prepare_reference_state(self): + """Returns circuit preparing the reference state of the ansatz (e.g prepare reference + wavefunction with HF, multi-reference state, etc). These preparations must be consistent + with the transform used to obtain the qubit operator. """ + + if self.default_reference_state not in self.supported_reference_state: + raise ValueError(f"Only supported reference state methods are: "\ + f"{self.supported_reference_state}") + if self.default_reference_state == "HF": + reference_state_circuit = get_qmf_circuit(self.var_params, True) + return reference_state_circuit + + def build_circuit(self, var_params=None): + """Build and return the quantum circuit implementing the state preparation ansatz + (with currently specified initial_state and var_params). """ + + if var_params is not None: + self.set_var_params(var_params) + elif self.var_params is None: + self.set_var_params() + + # Build the circuit for the QMF ansatz + self.circuit = self.prepare_reference_state() + + def update_var_params(self, var_params): + """Shortcut: set value of variational parameters in the already-built ansatz circuit member. + Preferable to rebuilt your circuit from scratch, which can be an involved process. """ + + # Rebuild the circuit because it is trivial + self.build_circuit(var_params) diff --git a/tangelo/toolboxes/ansatz_generator/rucc.py b/tangelo/toolboxes/ansatz_generator/rucc.py index c014a2b7a..edd2116b5 100644 --- a/tangelo/toolboxes/ansatz_generator/rucc.py +++ b/tangelo/toolboxes/ansatz_generator/rucc.py @@ -81,11 +81,10 @@ def set_var_params(self, var_params=None): elif var_params == "zeros": initial_var_params = np.zeros((self.n_var_params,), dtype=float) else: - try: - assert (len(var_params) == self.n_var_params) - initial_var_params = np.array(var_params) - except AssertionError: - raise ValueError(f"Expected {self.n_var_params} variational parameters but received {len(var_params)}.") + initial_var_params = np.array(var_params) + if initial_var_params.size != self.n_var_params: + raise ValueError(f"Expected {self.n_var_params} variational parameters but "\ + f"received {initial_var_params.size}.") self.var_params = initial_var_params return initial_var_params diff --git a/tangelo/toolboxes/ansatz_generator/tests/test_qcc.py b/tangelo/toolboxes/ansatz_generator/tests/test_qcc.py new file mode 100644 index 000000000..3f7750d23 --- /dev/null +++ b/tangelo/toolboxes/ansatz_generator/tests/test_qcc.py @@ -0,0 +1,215 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Unit tests for closed-shell and restricted open-shell qubit coupled cluster (QCC) ansatze. """ + +import unittest +import numpy as np + +from tangelo.linq import Simulator +from tangelo.toolboxes.ansatz_generator.qmf import QMF +from tangelo.toolboxes.ansatz_generator.qcc import QCC +from tangelo.toolboxes.operators.operators import QubitOperator +from tangelo.molecule_library import mol_H2_sto3g, mol_H4_cation_sto3g, mol_H4_doublecation_minao + +sim = Simulator() + + +class QCCTest(unittest.TestCase): + """Unit tests for various functionalities of the QCC ansatz class. Examples for both closed- + and restricted open-shell QCC are provided using H2, H4 +, and H4 2+ as well as for using the + QMF and QCC classes together. + """ + + @staticmethod + def test_qcc_set_var_params(): + """ Verify behavior of set_var_params for different inputs (keyword, list, numpy array). """ + + qcc_ansatz = QCC(mol_H2_sto3g) + + one_zero = np.zeros((1,), dtype=float) + + qcc_ansatz.set_var_params("zeros") + np.testing.assert_array_almost_equal(qcc_ansatz.var_params, one_zero, decimal=6) + + qcc_ansatz.set_var_params([0.]) + np.testing.assert_array_almost_equal(qcc_ansatz.var_params, one_zero, decimal=6) + + one_tenth = 0.1 * np.ones((1,)) + + qcc_ansatz.set_var_params([0.1]) + np.testing.assert_array_almost_equal(qcc_ansatz.var_params, one_tenth, decimal=6) + + qcc_ansatz.set_var_params(np.array([0.1])) + np.testing.assert_array_almost_equal(qcc_ansatz.var_params, one_tenth, decimal=6) + + def test_qcc_incorrect_number_var_params(self): + """ Return an error if user provide incorrect number of variational parameters """ + + qcc_ansatz = QCC(mol_H2_sto3g) + + self.assertRaises(ValueError, qcc_ansatz.set_var_params, np.array([1.] * 2)) + + def test_qcc_h2(self): + """ Verify closed-shell functionality when using the QCC class separately for H2 """ + + # Build the QCC ansatz, which sets the QMF parameters automatically if none are passed + qcc_var_params = [0.22613627] + qcc_op_list = [QubitOperator("X0 Y1 Y2 Y3")] + qcc_ansatz = QCC(mol_H2_sto3g, up_then_down=True, qubit_op_list=qcc_op_list) + + # Build a QMF + QCC circuit + qcc_ansatz.build_circuit() + + # Get qubit hamiltonian for energy evaluation + qubit_hamiltonian = qcc_ansatz.qubit_ham + + # Assert energy returned is as expected for given parameters + qcc_ansatz.update_var_params(qcc_var_params) + energy = sim.get_expectation_value(qubit_hamiltonian, qcc_ansatz.circuit) + self.assertAlmostEqual(energy, -1.1372701746609022, delta=1e-6) + + def test_qmf_qcc_h2(self): + """ Verify closed-shell functionality when using the QMF and QCC classes together for H2 """ + + # Build the QMF ansatz with optimized parameters + qmf_var_params = [3.14159265e+00, -2.42743256e-08, 3.14159266e+00, -3.27162543e-08, + 3.08514545e-09, 3.08514545e-09, 3.08514545e-09, 3.08514545e-09] + qmf_ansatz = QMF(mol_H2_sto3g, "JW", True) + qmf_ansatz.build_circuit(qmf_var_params) + + # Build the QCC ansatz with optimized QMF and QCC parameters and selected QCC generator + qcc_var_params = [-2.26136280e-01] + qcc_op_list = [QubitOperator("Y0 X1 X2 X3")] + qcc_ansatz = QCC(mol_H2_sto3g, "JW", True, qcc_op_list, qmf_ansatz.circuit) + + # Build a QMF + QCC circuit + qcc_ansatz.build_circuit() + + # Get qubit hamiltonian for energy evaluation + qubit_hamiltonian = qcc_ansatz.qubit_ham + + # Assert energy returned is as expected for the optimized QMF + QCC variational parameters + qcc_ansatz.update_var_params(qcc_var_params) + energy = sim.get_expectation_value(qubit_hamiltonian, qcc_ansatz.circuit) + self.assertAlmostEqual(energy, -1.137270174660901, delta=1e-6) + + def test_qcc_h4_cation(self): + """ Verify restricted open-shell functionality when using the QCC class for H4 + """ + + # Build the QCC ansatz, which sets the QMF parameters automatically if none are passed + qcc_op_list = [QubitOperator("X0 Y1 Y2 X3 X4 Y5"), QubitOperator("Y1 X3 X4 X5"), + QubitOperator("X0 Y1 Y3 Y4"), QubitOperator("X1 X2 Y3 X4 X5"), + QubitOperator("Y1 X2 Y3 Y4"), QubitOperator("Y1 X3 X4"), + QubitOperator("X0 Y2"), QubitOperator("X0 X1 Y3 X4 X5"), + QubitOperator("X0 X1 X2 Y3 X4")] + qcc_var_params = [ 0.26202301, -0.21102705, 0.11683144, -0.24234041, 0.13832747, + -0.0951985 , -0.03501809, 0.0640034 , 0.0542095] + qcc_ansatz = QCC(mol_H4_cation_sto3g, "SCBK", True, qcc_op_list) + + # Build a QMF + QCC circuit + qcc_ansatz.build_circuit() + + # Get qubit hamiltonian for energy evaluation + qubit_hamiltonian = qcc_ansatz.qubit_ham + + # Assert energy returned is as expected for given parameters + qcc_ansatz.update_var_params(qcc_var_params) + energy = sim.get_expectation_value(qubit_hamiltonian, qcc_ansatz.circuit) + self.assertAlmostEqual(energy, -1.6380901, delta=1e-6) + + def test_qmf_qcc_h4_cation(self): + """ Verify restricted open-shell functionality when using QMF + QCC ansatze for H4 + """ + + # Build the QMF ansatz with optimized parameters + qmf_var_params = [3.14159302e+00, 6.20193478e-07, 1.51226426e-06, 3.14159350e+00, + 3.14159349e+00, 7.88310582e-07, 3.96032530e+00, 2.26734374e+00, + 3.22127001e+00, 5.77997401e-01, 5.51422406e+00, 6.26513711e+00] + qmf_ansatz = QMF(mol_H4_cation_sto3g, "SCBK", True) + qmf_ansatz.build_circuit(qmf_var_params) + + # Build QCC ansatz with optimized QMF and QCC parameters and selected QCC generators + qcc_op_list = [QubitOperator("X0 X1 Y2 Y3 X4 Y5"), QubitOperator("Y1 Y3 Y4 X5"), + QubitOperator("X0 Y1 Y3 Y4"), QubitOperator("X1 X2 Y3 X4 X5"), + QubitOperator("Y1 Y2 Y3 X4"), QubitOperator("Y1 X3 X4"), + QubitOperator("Y0 X2"), QubitOperator("X0 X1 X3 X4 Y5"), + QubitOperator("X0 X1 X2 Y3 X4")] + qcc_var_params = [-0.26816042, 0.21694796, 0.12139543, -0.2293093, + -0.14577423, -0.08937818, 0.01796464, -0.06445363, 0.06056016] + qcc_ansatz = QCC(mol_H4_cation_sto3g, "SCBK", True, qcc_op_list, qmf_ansatz.circuit) + + # Build a QMF + QCC circuit + qcc_ansatz.build_circuit() + + # Get qubit hamiltonian for energy evaluation + qubit_hamiltonian = qcc_ansatz.qubit_ham + + # Assert energy returned is as expected for given parameters + qcc_ansatz.update_var_params(qcc_var_params) + energy = sim.get_expectation_value(qubit_hamiltonian, qcc_ansatz.circuit) + self.assertAlmostEqual(energy, -1.6382913, delta=1e-6) + + def test_qcc_h4_double_cation(self): + """ Verify restricted open-shell functionality when using the QCC class for H4 2+ """ + + # Build the QCC ansatz, which sets the QMF parameters automatically if none are passed + qcc_op_list = [QubitOperator("X0 Y2"), QubitOperator("Y0 X4"), QubitOperator("X0 Y6"), + QubitOperator("X0 Y5 X6"), QubitOperator("Y0 Y4 Y5")] + qcc_var_params = [0.27697283, -0.2531527, 0.05947973, -0.06943673, 0.07049098] + qcc_ansatz = QCC(mol_H4_doublecation_minao, "BK", False, qcc_op_list) + + # Build a QMF + QCC circuit + qcc_ansatz.build_circuit() + + # Get qubit hamiltonian for energy evaluation + qubit_hamiltonian = qcc_ansatz.qubit_ham + + # Assert energy returned is as expected for given parameters + qcc_ansatz.update_var_params(qcc_var_params) + energy = sim.get_expectation_value(qubit_hamiltonian, qcc_ansatz.circuit) + self.assertAlmostEqual(energy, -0.8547019, delta=1e-6) + + def test_qmf_qcc_h4_double_cation(self): + """ Verify restricted open-shell functionality when using QMF + QCC ansatze for H4 2+ """ + + # Build the QMF ansatz with optimized parameters + qmf_var_params = [3.14159247e+00, 3.14158884e+00, 1.37660700e-06, 3.14159264e+00, + 3.14159219e+00, 3.14158908e+00, 0.00000000e+00, 0.00000000e+00, + 6.94108155e-01, 1.03928030e-01, 5.14029803e+00, 2.81850365e+00, + 4.25403875e+00, 6.19640367e+00, 1.43241026e+00, 3.50279759e+00] + qmf_ansatz = QMF(mol_H4_doublecation_minao, "BK", True) + qmf_ansatz.build_circuit(qmf_var_params) + + # Build QCC ansatz with optimized QMF and QCC parameters and selected QCC generators + qcc_op_list = [QubitOperator("Y0 X4"), QubitOperator("X0 Y1 Y2 X4 Y5 X6"), + QubitOperator("Y0 Y1 Y4 X5"), QubitOperator("Y0 X1 Y4 Y5 X6"), + QubitOperator("X0 X1 X2 Y4 X5")] + qcc_var_params = [-2.76489925e-01, -2.52783324e-01, 5.76565629e-02, 6.99988237e-02, + -7.03721438e-02] + qcc_ansatz = QCC(mol_H4_doublecation_minao, "BK", True, qcc_op_list, qmf_ansatz.circuit) + + # Build a QMF + QCC circuit + qcc_ansatz.build_circuit() + + # Get qubit hamiltonian for energy evaluation + qubit_hamiltonian = qcc_ansatz.qubit_ham + + # Assert energy returned is as expected for given parameters + qcc_ansatz.update_var_params(qcc_var_params) + energy = sim.get_expectation_value(qubit_hamiltonian, qcc_ansatz.circuit) + self.assertAlmostEqual(energy, -0.85465810, delta=1e-6) + + +if __name__ == "__main__": + unittest.main() diff --git a/tangelo/toolboxes/ansatz_generator/tests/test_qmf.py b/tangelo/toolboxes/ansatz_generator/tests/test_qmf.py new file mode 100644 index 000000000..569a70969 --- /dev/null +++ b/tangelo/toolboxes/ansatz_generator/tests/test_qmf.py @@ -0,0 +1,141 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Unit tests for closed-shell and restricted open-shell qubit mean field (QMF) ansatz. """ + +import unittest +import numpy as np + +from tangelo.linq import Simulator +from tangelo.toolboxes.ansatz_generator.qmf import QMF +from tangelo.molecule_library import mol_H2_sto3g, mol_H4_sto3g, mol_H4_cation_sto3g + +sim = Simulator() + + +class QMFTest(unittest.TestCase): + """Unit tests of various functionalities of the QMF ansatz class. Examples for both closed- + and restricted open-shell QMF are provided using H2, H4, and H4+. + """ + + @staticmethod + def test_qmf_set_var_params(): + """ Verify behavior of set_var_params for different inputs (keyword, list, numpy array). """ + + qmf_ansatz = QMF(mol_H2_sto3g) + + eight_zeros = np.zeros((8,), dtype=float) + + qmf_ansatz.set_var_params("zeros") + np.testing.assert_array_almost_equal(qmf_ansatz.var_params, eight_zeros, decimal=6) + + qmf_ansatz.set_var_params([0.] * 8) + np.testing.assert_array_almost_equal(qmf_ansatz.var_params, eight_zeros, decimal=6) + + eight_pis = np.pi * np.ones((8,)) + + qmf_ansatz.set_var_params("pis") + np.testing.assert_array_almost_equal(qmf_ansatz.var_params, eight_pis, decimal=6) + + qmf_ansatz.set_var_params(np.array([np.pi] * 8)) + np.testing.assert_array_almost_equal(qmf_ansatz.var_params, eight_pis, decimal=6) + + def test_qmf_incorrect_number_var_params(self): + """ Return an error if user provide incorrect number of variational parameters """ + + qmf_ansatz = QMF(mol_H2_sto3g) + + self.assertRaises(ValueError, qmf_ansatz.set_var_params, np.array([1.] * 4)) + + @staticmethod + def test_qmf_set_params_upper_hf_state_h2(): + """ Verify closed-shell QMF functionalities for H2: upper case initial parameters """ + + qmf_ansatz = QMF(mol_H2_sto3g) + qmf_ansatz.set_var_params("HF_State") + + expected = np.array([np.pi] * 2 + [0.] * 6) + np.testing.assert_allclose(expected, qmf_ansatz.var_params, rtol=1e-10) + + @staticmethod + def test_qmf_set_params_lower_hf_state_h2(): + """ Verify closed-shell QMF functionalities for H2: lower case initial parameters """ + + qmf_ansatz = QMF(mol_H2_sto3g) + qmf_ansatz.set_var_params("hf_state") + + expected = np.array([np.pi] * 2 + [0.] * 6) + np.testing.assert_allclose(expected, qmf_ansatz.var_params, rtol=1e-10) + + @staticmethod + def test_qmf_set_params_hf_state_h4(): + """ Verify closed-shell QMF functionalities for H4: hf_state initial parameters """ + + qmf_ansatz = QMF(mol_H4_sto3g) + qmf_ansatz.set_var_params("hf_state") + + expected = np.array([np.pi] * 4 + [0.] * 12) + np.testing.assert_allclose(expected, qmf_ansatz.var_params, rtol=1e-10) + + def test_qmf_closed_h2(self): + """ Verify closed-shell QMF functionalities for H2 """ + + # Build ansatz and circuit + qmf_var_params = [np.pi] * 2 + [0.] * 6 + qmf_ansatz = QMF(mol_H2_sto3g) + qmf_ansatz.build_circuit() + + # Build qubit hamiltonian for energy evaluation + qubit_hamiltonian = qmf_ansatz.qubit_ham + + # Assert energy returned is as expected for given parameters + qmf_ansatz.update_var_params(qmf_var_params) + energy = sim.get_expectation_value(qubit_hamiltonian, qmf_ansatz.circuit) + self.assertAlmostEqual(energy, -1.1166843870853400, delta=1e-6) + + def test_qmf_closed_h4(self): + """ Verify closed-shell QMF functionalities for H4. """ + + # Build ansatz and circuit + qmf_var_params = [np.pi] * 4 + [0.] * 12 + qmf_ansatz = QMF(mol_H4_sto3g) + qmf_ansatz.build_circuit() + + # Build qubit hamiltonian for energy evaluation + qubit_hamiltonian = qmf_ansatz.qubit_ham + + # Assert energy returned is as expected for given parameters + qmf_ansatz.update_var_params(qmf_var_params) + energy = sim.get_expectation_value(qubit_hamiltonian, qmf_ansatz.circuit) + self.assertAlmostEqual(energy, -1.7894832518559973, delta=1e-6) + + def test_qmf_open_h4_cation(self): + """ Verify open-shell QMF functionalities for H4 + """ + + # Build ansatz and circuit + qmf_var_params = [np.pi] * 4 + [0.] * 12 + qmf_ansatz = QMF(mol_H4_cation_sto3g) + qmf_ansatz.build_circuit() + + # Build qubit hamiltonian for energy evaluation + qubit_hamiltonian = qmf_ansatz.qubit_ham + + # Assert energy returned is as expected for given parameters + energy = sim.get_expectation_value(qubit_hamiltonian, qmf_ansatz.circuit) + qmf_ansatz.update_var_params(qmf_var_params) + self.assertAlmostEqual(energy, -1.5859184313544759, delta=1e-6) + + +if __name__ == "__main__": + unittest.main() diff --git a/tangelo/toolboxes/ansatz_generator/uccsd.py b/tangelo/toolboxes/ansatz_generator/uccsd.py index 6257e0a33..55c9452c0 100644 --- a/tangelo/toolboxes/ansatz_generator/uccsd.py +++ b/tangelo/toolboxes/ansatz_generator/uccsd.py @@ -119,11 +119,10 @@ def set_var_params(self, var_params=None): elif var_params == "mp2": initial_var_params = self._compute_mp2_params() else: - try: - assert (len(var_params) == self.n_var_params) - initial_var_params = np.array(var_params) - except AssertionError: - raise ValueError(f"Expected {self.n_var_params} variational parameters but received {len(var_params)}.") + initial_var_params = np.array(var_params) + if initial_var_params.size != self.n_var_params: + raise ValueError(f"Expected {self.n_var_params} variational parameters but "\ + f"received {initial_var_params.size}.") self.var_params = initial_var_params return initial_var_params diff --git a/tangelo/toolboxes/ansatz_generator/upccgsd.py b/tangelo/toolboxes/ansatz_generator/upccgsd.py index 36ce9f1c3..415c44c9c 100644 --- a/tangelo/toolboxes/ansatz_generator/upccgsd.py +++ b/tangelo/toolboxes/ansatz_generator/upccgsd.py @@ -100,11 +100,10 @@ def set_var_params(self, var_params=None): elif var_params == "random": initial_var_params = 1.e-1 * (np.random.random((self.n_var_params,)) - 0.5) else: - try: - assert (len(var_params) == self.n_var_params) - initial_var_params = np.array(var_params) - except AssertionError: - raise ValueError(f"Expected {self.n_var_params} variational parameters but received {len(var_params)}.") + initial_var_params = np.array(var_params) + if initial_var_params.size != self.n_var_params: + raise ValueError(f"Expected {self.n_var_params} variational parameters but "\ + f"received {initial_var_params.size}.") self.var_params = initial_var_params return initial_var_params diff --git a/tangelo/toolboxes/ansatz_generator/variational_circuit.py b/tangelo/toolboxes/ansatz_generator/variational_circuit.py index fcb5cf22f..3bdd70089 100644 --- a/tangelo/toolboxes/ansatz_generator/variational_circuit.py +++ b/tangelo/toolboxes/ansatz_generator/variational_circuit.py @@ -61,13 +61,11 @@ def set_var_params(self, var_params=None): elif var_params == "zeros": var_params = np.zeros((self.n_var_params,), dtype=float) else: - try: - assert (len(var_params) == self.n_var_params) - var_params = np.array(var_params) - except AssertionError: - raise ValueError(f"Expected {self.n_var_params} variational parameters but received {len(var_params)}.") + var_params = np.array(var_params) + if var_params.size != self.n_var_params: + raise ValueError(f"Expected {self.n_var_params} variational parameters but "\ + f"received {var_params.size}.") self.var_params = var_params - return var_params def update_var_params(self, var_params): From dcc06b9b5ed2da274ef81837e325cff867f2e155 Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Wed, 8 Dec 2021 21:11:19 +0100 Subject: [PATCH 21/68] Docs aesthetics: temporary Tangelo theme to docs, fixed some visuals in notebooks (#94) * Fixed doc script, added css and img for temporary doc customization * Fixed some stuff that wasn't rendering well in the notebooks Co-authored-by: AlexandreF-1qbit <76115575+AlexandreF-1qbit@users.noreply.github.com> --- README.rst | 24 +- dev_tools/build_sphinx_docs.sh | 6 +- dev_tools/requirements.txt | 1 - docs/source/_static/css/custom.css | 15 + docs/source/_static/img/tangelo_logo.png | Bin 0 -> 159002 bytes docs/source/_static/img/tangelo_name.png | Bin 0 -> 49439 bytes .../_static/img/tangelo_name_colorfont.png | Bin 0 -> 49410 bytes .../_static/img/tangelo_name_gradient.png | Bin 0 -> 67838 bytes .../source/_static/img/tangelo_name_white.png | Bin 0 -> 29668 bytes docs/source/conf.py | 5 + examples/dmet.ipynb | 734 ++---------------- examples/linq/1.the_basics.ipynb | 8 +- examples/linq/3.noisy_simulation.ipynb | 6 +- examples/problem_decomposition_oniom.ipynb | 75 +- ...st_cloud_hardware_experiments_braket.ipynb | 7 +- examples/vqe.ipynb | 36 +- 16 files changed, 167 insertions(+), 750 deletions(-) create mode 100644 docs/source/_static/css/custom.css create mode 100644 docs/source/_static/img/tangelo_logo.png create mode 100644 docs/source/_static/img/tangelo_name.png create mode 100644 docs/source/_static/img/tangelo_name_colorfont.png create mode 100644 docs/source/_static/img/tangelo_name_gradient.png create mode 100644 docs/source/_static/img/tangelo_name_white.png diff --git a/README.rst b/README.rst index 9dca749cb..b05faea30 100644 --- a/README.rst +++ b/README.rst @@ -2,26 +2,18 @@ Tangelo overview ============= Welcome ! -This open-source quantum SDK provides tools developed for exploring quantum chemistry simulation end-to-end workflows on -both gate-model quantum computers and quantum circuit emulators. -It was designed to support the development of quantum algorithms and workflows running by providing building-blocks from various toolboxes. -It attempts to cover all steps of the process, such as quantum execution, pre- and post-processing techniques, including problem decomposition. -It provides users with features such as resource estimation, access to various compute backends (noiseless and noisy simulators, -quantum devices) and some classical solvers in order to keep track of both accuracy and resource requirements of our workflows, -and facilitate the design of successful hardware experiments. +Tangelo is an open-source python package developed by Good Chemistry Company, focused on the development of end-to-end material simulation workflows on quantum computers. Its modular design and ease-of-use enables users to easily assemble custom workflows, tinker and define their own building blocks, while keeping track of quantum resource requirements, such as number of qubits, gates or measurements. Through problem decomposition techniques, users can scale up beyond toy models and study the impact of quantum computing on more industrially-relevant use cases. Tangelo is backend-agnostic and compatible with many existing open-source frameworks, making the integration of third-party tools such as state-of-the-art simulators, circuit compilers or quantum cloud services straightforward. It is our wish to develop a community around Tangelo, collaborate, and together leverage the best of what the field has to offer. + Install ------- -This package requires a Python 3 environment. - -We recommend: - +This package requires a Python 3 environment. We recommend: * using `Python virtual environments `_ in order to set up your environment safely and cleanly * installing the "dev" version of Python3 if you encounter missing header errors, such as ``python.h file not found``. -* having good C/C++ compilers and BLAS library to ensure the quantum circuit simulators you choose to install have good performance. +* having good C/C++ compilers and BLAS libraries to ensure good overall performance of computation-intensive code. Using pip ^^^^^^^^^ @@ -31,7 +23,7 @@ TODO: once this package is available on pypi, give the command. From source, using setuptools ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -This package can be installed by first cloning this repository with ``git clone``\ , and typing the following command in the +This package can be installed by downloading or cloning the contents of this repository, and typing the following command in the root directory: .. code-block:: @@ -41,12 +33,11 @@ root directory: If the installation of a dependency fails and the reason is not obvious, we suggest installing that dependency separately with ``pip``\ , before trying again. -You can also add the path to this folder to your ``PYTHONPATH`` environment variable to facilitate your own developments. Optional dependencies ^^^^^^^^^^^^^^^^^^^^^ -Tangelo enables users to target various backends. In particular, it integrates quantum circuit simulators such as +Tangelo enables users to target various backends. In particular, it integrates quantum circuit simulators such as ``qulacs``\ , ``qiskit``\ , ``cirq`` or ``qdk``. We leave it to you to install the packages of your choice. Most packages can be installed through pip in a straightforward way: @@ -80,7 +71,8 @@ Tutorials --------- Please check the ``examples`` folder jupyter notebook tutorials and other examples. -TODO: recommend here the one we are doing about the DMET paper once its ready and merged + + Tests ----- diff --git a/dev_tools/build_sphinx_docs.sh b/dev_tools/build_sphinx_docs.sh index 2ca52487d..537f66844 100644 --- a/dev_tools/build_sphinx_docs.sh +++ b/dev_tools/build_sphinx_docs.sh @@ -2,7 +2,6 @@ # Make sure you build tangelo and all relevant dependencies before attempting to generate documentations # or mock the desired packages in docs/source/conf.py -cd ../docs || cd docs pip install sphinx sphinx_rtd_theme nbsphinx # This environment seems to work for building the docs. @@ -13,5 +12,8 @@ pip install -r requirements.txt sudo apt-get install -y latexmk texlive-latex-recommended texlive-latex-extra texlive-fonts-recommended pandoc dvipng # Build html documentation in ../docs/source/html -sphinx-apidoc -o source ../tangelo +# Make sure Tangelo is installed beforehand +cd ../docs || cd docs +sphinx-apidoc -o ./source ../tangelo make clean; make html +cd - diff --git a/dev_tools/requirements.txt b/dev_tools/requirements.txt index ed010efb7..304513540 100644 --- a/dev_tools/requirements.txt +++ b/dev_tools/requirements.txt @@ -136,7 +136,6 @@ pytket-qiskit==0.13.0 pytz==2021.1 pyzmq==19.0.2 qcs-api-client==0.8.0 -qemist-client===0.2.1-cb867ac6 qtconsole==5.1.1 QtPy==1.11.2 Quandl==3.6.0 diff --git a/docs/source/_static/css/custom.css b/docs/source/_static/css/custom.css new file mode 100644 index 000000000..a9253ebfb --- /dev/null +++ b/docs/source/_static/css/custom.css @@ -0,0 +1,15 @@ +body { + --themecolor: rgb(217,100, 0); + background: rgb(100, 200, 10); + background-image: url("./_static/img/tangelo_logo.png"); /* Ignored by sphinx */ +} + +h1, h2, h3, h4 { + color: rgb(217, 100, 0); +} + + +.wy-side-nav-search, .wy-nav-top { + background: linear-gradient(0deg, rgb(217, 100, 0), rgb(255,183,0)); + /*var(--themecolor);*/ +} diff --git a/docs/source/_static/img/tangelo_logo.png b/docs/source/_static/img/tangelo_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..68faab1bf88d224d3e71aab8f90167a6a6a1b0fe GIT binary patch literal 159002 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

&u6(zwoYMaNYA>or6T1{!l%77O`)We%O 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Ŏm{`0Ki(9$11j2Q{J&L<|K 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 literal 0 HcmV?d00001 diff --git a/docs/source/_static/img/tangelo_name.png b/docs/source/_static/img/tangelo_name.png new file mode 100644 index 0000000000000000000000000000000000000000..0556af66df0382191431a409396c8011ecc8ad51 GIT binary patch literal 49439 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 FzW@Okrj`Hz literal 0 HcmV?d00001 diff --git a/docs/source/_static/img/tangelo_name_colorfont.png b/docs/source/_static/img/tangelo_name_colorfont.png new file mode 100644 index 0000000000000000000000000000000000000000..c6403e68e3057721f4eb0cc9061608063cd61d98 GIT binary patch literal 49410 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 literal 0 HcmV?d00001 diff --git a/docs/source/_static/img/tangelo_name_gradient.png b/docs/source/_static/img/tangelo_name_gradient.png new file mode 100644 index 0000000000000000000000000000000000000000..bbcfe30cc7af878b9aff90cdf9b9547b4d77e32f GIT binary patch literal 67838 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 literal 0 HcmV?d00001 diff --git a/docs/source/_static/img/tangelo_name_white.png b/docs/source/_static/img/tangelo_name_white.png new file mode 100644 index 0000000000000000000000000000000000000000..3aca2e604af1a438f5287a3d43762f603062b825 GIT binary patch literal 29668 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 bQOe+~6Eo#7;kknf<)1lq_9Xd)*Y*DgHnAnf literal 0 HcmV?d00001 diff --git a/docs/source/conf.py b/docs/source/conf.py index 40106912d..810935456 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -48,11 +48,16 @@ # html_theme = 'sphinx_rtd_theme' +#def setup (app): +# app.add_css_file('css/custom.css') + napoleon_custom_sections = [('Returns', 'params_style')] # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] +html_css_files = ['css/custom.css'] # works (path relative to html_static_path) +html_logo = './_static/img/tangelo_name_white.png' # works (path relative to conf.py) autodoc_mock_imports = ['qemist-client', 'PIL', 'Pillow', 'qsharp'] diff --git a/examples/dmet.ipynb b/examples/dmet.ipynb index 9ec9a0930..b4692a49e 100755 --- a/examples/dmet.ipynb +++ b/examples/dmet.ipynb @@ -197,7 +197,7 @@ "\n", "\t\tFragment Number : # 4\n", "\t\t------------------------\n", - "\t\tFragment Energy = -72.0631745663\n", + "\t\tFragment Energy = -72.0631745662\n", "\t\tNumber of Electrons in Fragment = 16.0000000000\n", "\n", " \tIteration = 2\n", @@ -233,7 +233,7 @@ "\n", "\t\tFragment Number : # 2\n", "\t\t------------------------\n", - "\t\tFragment Energy = -72.4061095743\n", + "\t\tFragment Energy = -72.4061095744\n", "\t\tNumber of Electrons in Fragment = 14.0000000000\n", "\n", "\t\tFragment Number : # 3\n", @@ -270,9 +270,9 @@ "\t\tNumber of Electrons in Fragment = 16.0000000000\n", "\n", " \t*** DMET Cycle Done *** \n", - " \tDMET Energy ( a.u. ) = -157.1137415265\n", + " \tDMET Energy ( a.u. ) = -157.1137415264\n", " \tChemical Potential = -0.0004124578\n", - "DMET energy (hartree): \t -157.11374152645877\n" + "DMET energy (hartree): \t -157.11374152644245\n" ] } ], @@ -297,8 +297,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "Correlation energy (hartree): \t 0.26456372348172863\n", - "Correlation energy (kcal/mol): \t 166.01373648478472\n" + "Correlation energy (hartree): \t 0.26456372346564194\n", + "Correlation energy (kcal/mol): \t 166.01373647469032\n" ] } ], @@ -361,15 +361,7 @@ "cell_type": "code", "execution_count": 7, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "converged SCF energy = -5.26412066516912\n" - ] - } - ], + "outputs": [], "source": [ "options_h10_vqe = {\"molecule\": mol_h10, \"qubit_mapping\": \"jw\", \"verbose\": False}\n", "vqe_h10 = VQESolver(options_h10_vqe)\n", @@ -395,11 +387,11 @@ "output_type": "stream", "text": [ "{\n", - " \"qubit_hamiltonian_terms\": 4435,\n", + " \"qubit_hamiltonian_terms\": 4479,\n", " \"circuit_width\": 20,\n", - " \"circuit_gates\": 62502,\n", - " \"circuit_2qubit_gates\": 40320,\n", - " \"circuit_var_gates\": 2508,\n", + " \"circuit_gates\": 66086,\n", + " \"circuit_2qubit_gates\": 42752,\n", + " \"circuit_var_gates\": 2636,\n", " \"vqe_variational_parameters\": 350\n", "}\n" ] @@ -475,7 +467,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Compared to a direct VQE algorithm, those resources are greatly reduced: from 20 qubits down to only 4 qubits in our case. Below, `dmet_h10.simulate()` computes the DMET-VQE energy." + "Compared to a direct VQE algorithm, those resources are greatly reduced: from 20 qubits down to only 4 qubits in our case. Below, `dmet_h10.simulate()` computes the DMET-VQE energy. \n", + "\n", + "The options currently selected specify that VQE must be run for each fragment, at each iteration of DMET: as such, it may take 2-3 minutes for this cell to finish. The `verbose` option is turned off to hide the lengthy prints: feel free to turn it back on to track the progress of this cell, if you'd like." ] }, { @@ -487,645 +481,12 @@ "name": "stdout", "output_type": "stream", "text": [ - " \tIteration = 1\n", - " \t----------------\n", - " \n", - "\t\tFragment Number : # 1\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.686237164534027\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8000328075+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000+0.0000000000j\n", - "\n", - "\t\tFragment Number : # 2\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6862371644300944\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8001880723-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - "\t\tFragment Number : # 3\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6862371644960692\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8000328072+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - "\t\tFragment Number : # 4\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.686237164427009\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8001880724-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - "\t\tFragment Number : # 5\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6862371644301024\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8001880721-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000+0.0000000000j\n", - "\n", - "\t\tFragment Number : # 6\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.686237164534023\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8000328074+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - "\t\tFragment Number : # 7\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.686237164430088\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8001880724-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000+0.0000000000j\n", - "\n", - "\t\tFragment Number : # 8\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6862371644270122\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8001880725+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - "\t\tFragment Number : # 9\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6862371644270093\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8001880724-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - "\t\tFragment Number : # 10\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6862371644300982\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8001880722-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000+0.0000000000j\n", - "\n", - " \tIteration = 2\n", - " \t----------------\n", - " \n", - "\t\tFragment Number : # 1\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6863371804140288\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8001738751-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - "\t\tFragment Number : # 2\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6863371803791534\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8001738751+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - "\t\tFragment Number : # 3\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.686337180376079\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8001738750-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - "\t\tFragment Number : # 4\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6863371803092795\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8003291330+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - "\t\tFragment Number : # 5\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.686337180379148\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8001738749+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000+0.0000000000j\n", - "\n", - "\t\tFragment Number : # 6\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6863371804140241\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8001738751-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000+0.0000000000j\n", - "\n", - "\t\tFragment Number : # 7\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6863371803791525\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8001738750-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - "\t\tFragment Number : # 8\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6863371803092753\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8003291332+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - "\t\tFragment Number : # 9\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6863371803092804\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8003291330-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000+0.0000000000j\n", - "\n", - "\t\tFragment Number : # 10\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6863371803791511\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8001738750-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - " \tIteration = 3\n", - " \t----------------\n", - " \n", - "\t\tFragment Number : # 1\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6860132738938955\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.7998722247+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - "\t\tFragment Number : # 2\n", - "\t\t------------------------\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6860132738590141\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.7998722249+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - "\t\tFragment Number : # 3\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.686013273855947\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.7998722248+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000+0.0000000000j\n", - "\n", - "\t\tFragment Number : # 4\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6860132739301061\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.7997169439+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000+0.0000000000j\n", - "\n", - "\t\tFragment Number : # 5\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6860132738590212\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.7998722247-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000+0.0000000000j\n", - "\n", - "\t\tFragment Number : # 6\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6860132738938942\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.7998722248+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - "\t\tFragment Number : # 7\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6860132738590274\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.7998722246+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000+0.0000000000j\n", - "\n", - "\t\tFragment Number : # 8\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6860132739300904\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.7997169435-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - "\t\tFragment Number : # 9\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6860132739300926\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.7997169436+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000+0.0000000000j\n", - "\n", - "\t\tFragment Number : # 10\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.686013273859022\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.7998722248-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - " \tIteration = 4\n", - " \t----------------\n", - " \n", - "\t\tFragment Number : # 1\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6861273197784212\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8000331253+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - "\t\tFragment Number : # 2\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6861273197435434\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8000331254-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000+0.0000000000j\n", - "\n", - "\t\tFragment Number : # 3\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.686127319740479\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8000331252-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - "\t\tFragment Number : # 4\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.686127319812038\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.7998778524-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000+0.0000000000j\n", - "\n", - "\t\tFragment Number : # 5\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6861273197435445\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8000331254+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000+0.0000000000j\n", - "\n", - "\t\tFragment Number : # 6\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6861273197784097\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8000331255+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000+0.0000000000j\n", - "\n", - "\t\tFragment Number : # 7\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.686127319743552\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8000331252-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - "\t\tFragment Number : # 8\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.686127319812043\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.7998778525+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000+0.0000000000j\n", - "\n", - "\t\tFragment Number : # 9\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6861273198120306\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.7998778522+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000+0.0000000000j\n", - "\n", - "\t\tFragment Number : # 10\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6861273197435502\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8000331252-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000+0.0000000000j\n", - "\n", - " \tIteration = 5\n", - " \t----------------\n", - " \n", - "\t\tFragment Number : # 1\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6861118196762586\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8000112590-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - "\t\tFragment Number : # 2\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6861118196413993\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8000112587+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - "\t\tFragment Number : # 3\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6861118196383234\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8000112587-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000+0.0000000000j\n", - "\n", - "\t\tFragment Number : # 4\n", - "\t\t------------------------\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6861118197102312\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.7998559846+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - "\t\tFragment Number : # 5\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6861118196413911\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8000112588+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000+0.0000000000j\n", - "\n", - "\t\tFragment Number : # 6\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6861118196762737\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8000112586-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - "\t\tFragment Number : # 7\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6861118196413982\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8000112587-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000+0.0000000000j\n", - "\n", - "\t\tFragment Number : # 8\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.686111819710226\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.7998559844-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000+0.0000000000j\n", - "\n", - "\t\tFragment Number : # 9\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6861118197102336\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.7998559847+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - "\t\tFragment Number : # 10\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6861118196414027\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8000112586-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - " \tIteration = 6\n", - " \t----------------\n", - " \n", - "\t\tFragment Number : # 1\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6861118197232579\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8000112587+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - "\t\tFragment Number : # 2\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.686111819688388\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8000112586-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - "\t\tFragment Number : # 3\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6861118196853098\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8000112587+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000+0.0000000000j\n", - "\n", - "\t\tFragment Number : # 4\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6861118197572291\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.7998559850-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000+0.0000000000j\n", - "\n", - "\t\tFragment Number : # 5\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6861118196883835\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8000112587+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000+0.0000000000j\n", - "\n", - "\t\tFragment Number : # 6\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6861118197232563\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8000112587-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000+0.0000000000j\n", - "\n", - "\t\tFragment Number : # 7\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6861118196883909\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8000112585-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - "\t\tFragment Number : # 8\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6861118197572211\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.7998559848+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - "\t\tFragment Number : # 9\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6861118197572202\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.7998559848+0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000+0.0000000000j\n", - "\n", - "\t\tFragment Number : # 10\n", - "\t\t------------------------\n", - "Optimization terminated successfully. (Exit mode 0)\n", - " Current function value: -1.6861118196883809\n", - " Iterations: 5\n", - " Function evaluations: 25\n", - " Gradient evaluations: 5\n", - "\t\tFragment Energy = -1.8000112588-0.0000000000j\n", - "\t\tNumber of Electrons in Fragment = 2.0000000000-0.0000000000j\n", - "\n", - " \t*** DMET Cycle Done *** \n", - " \tDMET Energy ( a.u. ) = -5.3675235928+0.0000000000j\n", - " \tChemical Potential = -0.0001253395-0.0000000000j\n", - "DMET energy (hartree): \t (-5.367523592778689+2.9183857188542833e-32j)\n" + "DMET energy (hartree): \t -5.367523592518705\n" ] } ], "source": [ - "dmet_h10.verbose = True\n", + "dmet_h10.verbose = False\n", "energy_h10_dmet = dmet_h10.simulate()\n", "\n", "print(f\"DMET energy (hartree): \\t {energy_h10_dmet}\")" @@ -1176,10 +537,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "Difference FCI vs HF energies (hartree): \t\t 0.11680533556176087\n", - "Difference FCI vs DMET-VQE energies (hartree): \t\t 0.013402407952192164\n", - "Difference FCI vs HF energies (kcal/mol): \t\t 73.29534806500494\n", - "Difference FCI vs DMET-VQE energies (kcal/mol): \t 8.410010990000583\n" + "Difference FCI vs HF energies (hartree): \t\t 0.11680533556175732\n", + "Difference FCI vs DMET-VQE energies (hartree): \t\t 0.013402408212176198\n", + "Difference FCI vs HF energies (kcal/mol): \t\t 73.29534806500271\n", + "Difference FCI vs DMET-VQE energies (kcal/mol): \t 8.410011153140564\n" ] } ], @@ -1210,10 +571,10 @@ "outputs": [], "source": [ "H4 = \"\"\"\n", - "H 0.7071 0.0 0.0\n", - "H 0.0 0.7071 0.0\n", - "H -1.0071 0.0 0.0\n", - "H 0.0 -1.0071 0.0\n", + "H 0.7071 0. 0.\n", + "H 0. 0.7071 0.\n", + "H -1.0071 0. 0.\n", + "H 0. -1.0071 0.\n", "\"\"\"\n", "\n", "mol_h4 = SecondQuantizedMolecule(H4, q=0, spin=0, basis=\"3-21g\")" @@ -1226,6 +587,7 @@ "### 5.1 Localization method\n", "\n", "Electron localization is used to define the bath and the environment orbitals. There are two options available:\n", + "\n", "- `Localization.meta_lowdin` (default): Described in Q. Sun et al., JCTC 10, 3784-3790 (2014).\n", "- `Localization.iao`: Described in G. Knizia, JCTC 9, 4834-4843 (2013). This algorithm maps the orbitals to an minao set. So, at least a double zeta basis set must be used with this localization method." ] @@ -1238,15 +600,15 @@ { "data": { "text/plain": [ - "{'molecule': ,\n", + "{'molecule': ,\n", " 'electron_localization': ,\n", " 'fragment_atoms': [1, 1, 1, 1],\n", " 'fragment_solvers': ['ccsd', 'ccsd', 'ccsd', 'ccsd'],\n", - " 'optimizer': >,\n", + " 'optimizer': >,\n", " 'initial_chemical_potential': 0.0,\n", " 'solvers_options': [{}, {}, {}, {}],\n", " 'verbose': False,\n", - " 'mean_field': ,\n", + " 'mean_field': ,\n", " 'chemical_potential': None,\n", " 'dmet_energy': None,\n", " 'orbitals': None,\n", @@ -1264,8 +626,7 @@ "options_h4_dmet = {\"molecule\": mol_h4,\n", " \"fragment_atoms\": [1]*4,\n", " \"electron_localization\": Localization.iao,\n", - " \"verbose\": False\n", - " }\n", + " \"verbose\": False}\n", "\n", "dmet_h4 = DMETProblemDecomposition(options_h4_dmet)\n", "vars(dmet_h4)" @@ -1288,11 +649,11 @@ { "data": { "text/plain": [ - "{'molecule': ,\n", + "{'molecule': ,\n", " 'electron_localization': ,\n", " 'fragment_atoms': [1, 1, 1, 1],\n", " 'fragment_solvers': ['vqe', 'ccsd', 'fci', 'vqe'],\n", - " 'optimizer': >,\n", + " 'optimizer': >,\n", " 'initial_chemical_potential': 0.0,\n", " 'solvers_options': [{'qubit_mapping': 'jw',\n", " 'initial_var_params': 'ones',\n", @@ -1301,7 +662,7 @@ " {},\n", " {'qubit_mapping': 'jw', 'initial_var_params': 'ones', 'verbose': False}],\n", " 'verbose': False,\n", - " 'mean_field': ,\n", + " 'mean_field': ,\n", " 'chemical_potential': None,\n", " 'dmet_energy': None,\n", " 'orbitals': None,\n", @@ -1319,8 +680,7 @@ "options_h4_dmet = {\"molecule\": mol_h4,\n", " \"fragment_atoms\": [1]*4,\n", " \"fragment_solvers\": [\"vqe\", \"ccsd\", \"fci\", \"vqe\"],\n", - " \"verbose\": False\n", - " }\n", + " \"verbose\": False}\n", "\n", "dmet_h4 = DMETProblemDecomposition(options_h4_dmet)\n", "vars(dmet_h4)" @@ -1343,15 +703,15 @@ { "data": { "text/plain": [ - "{'molecule': ,\n", + "{'molecule': ,\n", " 'electron_localization': ,\n", " 'fragment_atoms': [1, 1, 1, 1],\n", " 'fragment_solvers': ['ccsd', 'ccsd', 'ccsd', 'ccsd'],\n", - " 'optimizer': >,\n", + " 'optimizer': >,\n", " 'initial_chemical_potential': 0.1,\n", " 'solvers_options': [{}, {}, {}, {}],\n", " 'verbose': False,\n", - " 'mean_field': ,\n", + " 'mean_field': ,\n", " 'chemical_potential': None,\n", " 'dmet_energy': None,\n", " 'orbitals': None,\n", @@ -1369,8 +729,7 @@ "options_h4_dmet = {\"molecule\": mol_h4,\n", " \"fragment_atoms\": [1]*4,\n", " \"initial_chemical_potential\" : 0.1,\n", - " \"verbose\": False\n", - " }\n", + " \"verbose\": False}\n", "\n", "dmet_h4 = DMETProblemDecomposition(options_h4_dmet)\n", "vars(dmet_h4)" @@ -1393,18 +752,18 @@ { "data": { "text/plain": [ - "{'molecule': ,\n", + "{'molecule': ,\n", " 'electron_localization': ,\n", " 'fragment_atoms': [1, 1, 1, 1],\n", " 'fragment_solvers': ['vqe', 'vqe', 'vqe', 'vqe'],\n", - " 'optimizer': >,\n", + " 'optimizer': >,\n", " 'initial_chemical_potential': 0.0,\n", " 'solvers_options': [{'qubit_mapping': 'bk'},\n", " {'qubit_mapping': 'bk'},\n", " {'qubit_mapping': 'bk'},\n", " {'qubit_mapping': 'bk'}],\n", " 'verbose': False,\n", - " 'mean_field': ,\n", + " 'mean_field': ,\n", " 'chemical_potential': None,\n", " 'dmet_energy': None,\n", " 'orbitals': None,\n", @@ -1425,8 +784,7 @@ " \"fragment_atoms\": [1]*4,\n", " \"fragment_solvers\": \"vqe\",\n", " \"solvers_options\": vqe_options, \n", - " \"verbose\": False\n", - " }\n", + " \"verbose\": False}\n", "\n", "dmet_h4 = DMETProblemDecomposition(options_h4_dmet)\n", "vars(dmet_h4)" @@ -1439,11 +797,15 @@ "## 6. Closing words\n", "\n", "This concludes our overview of `DMETProblemDecomposition`. There are many flavors of DMET and only one has been discussed here. Here we refer some papers relevant for the reader who wants more details:\n", + "\n", "- Theory\n", + "\n", " - S. Wouters, C.A. Jiménez-Hoyos, Q. Sun, and G.K.L. Chan, J. Chem. Theory Comput. 12, 2706 (2016).\n", " - G. Knizia and G.K.L. Chan, J. Chem. Theory Comput. 9, 1428 (2013).\n", " - G. Knizia and G.K.L. Chan, Phys. Rev. Lett. 109, 186404 (2012).\n", + " \n", "- Good Chemistry Company papers on the subject\n", + "\n", " - Y. Kawashima, M.P. Coons, Y. Nam, E. Lloyd, S. Matsuura, A.J. Garza, S. Johri, L. Huntington, V. Senicourt, A.O. Maksymov, J.H. V. Nguyen, J. Kim, N. Alidoust, A. Zaribafiyan, and T. Yamazaki, ArXiv:2102.07045 (2021).\n", " - T. Yamazaki, S. Matsuura, A. Narimani, A. Saidmuradov, and A. Zaribafiyan, ArXiv:1806.01305 (2018)." ] @@ -1451,9 +813,9 @@ ], "metadata": { "kernelspec": { - "display_name": "Tangelo", + "display_name": "qsdk", "language": "python", - "name": "tangelo" + "name": "qsdk" }, "language_info": { "codemirror_mode": { @@ -1465,7 +827,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.0" + "version": "3.9.9" } }, "nbformat": 4, diff --git a/examples/linq/1.the_basics.ipynb b/examples/linq/1.the_basics.ipynb index 7d1accddc..ba98f63e8 100755 --- a/examples/linq/1.the_basics.ipynb +++ b/examples/linq/1.the_basics.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# BackendBuddy: the basics" + "# Linq: the basics" ] }, { @@ -859,9 +859,9 @@ ], "metadata": { "kernelspec": { - "display_name": "tangelo_sept_21", + "display_name": "tangelo_docs_aesthetics", "language": "python", - "name": "tangelo_sept_21" + "name": "tangelo_docs_aesthetics" }, "language_info": { "codemirror_mode": { @@ -873,7 +873,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.0" + "version": "3.8.10" } }, "nbformat": 4, diff --git a/examples/linq/3.noisy_simulation.ipynb b/examples/linq/3.noisy_simulation.ipynb index a8846646f..65afac82c 100755 --- a/examples/linq/3.noisy_simulation.ipynb +++ b/examples/linq/3.noisy_simulation.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# BackendBuddy: noisy simulation" + "# Linq: noisy simulation" ] }, { @@ -536,7 +536,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -550,7 +550,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.0" + "version": "3.8.10" } }, "nbformat": 4, diff --git a/examples/problem_decomposition_oniom.ipynb b/examples/problem_decomposition_oniom.ipynb index ca5b842de..0d60f95c4 100755 --- a/examples/problem_decomposition_oniom.ipynb +++ b/examples/problem_decomposition_oniom.ipynb @@ -9,10 +9,10 @@ "## Table of contents:\n", "* [1. Introduction](#1)\n", "* [2. Use case - water absorption](#2)\n", - ">* [2.1 Why water absorption is a relevant scientific problem?](#21)\n", - ">* [2.2 Why using ONIOM?](#22)\n", - ">* [2.3 How is it done?](#23)\n", - ">* [2.4 Potential energy scan for an hydrogen bond](#24)\n", + " * [2.1 Why water absorption is a relevant scientific problem?](#21)\n", + " * [2.2 Why using ONIOM?](#22)\n", + " * [2.3 How is it done?](#23)\n", + " * [2.4 Potential energy scan for an hydrogen bond](#24)\n", "* [3. Closing words](#3)\n" ] }, @@ -27,9 +27,7 @@ "\n", "Formally, the energy evaluated by ONIOM (with a low- and high-accuracy methods) is expressed as:\n", "\n", - "\\begin{equation}\n", - "E_{ONIOM} = E_{All}^{Low} + \\sum_{i=1}^N (E_{Fragment\\ i}^{High} - E_{Fragment\\ i}^{Low})\n", - "\\end{equation}\n", + "$$E_{ONIOM} = E_{All}^{Low} + \\sum_{i=1}^N (E_{Fragment\\ i}^{High} - E_{Fragment\\ i}^{Low})$$\n", "\n", "Where $E_{All}$, $E_{Fragment\\ i}$ are respectively the energy of the whole system and the energy of a fragment labelled $i$. The general procedure for ONIOM is as follows. The user identifies a system of interest and a low-cost method is used to compute its total energy ($E_{All}^{Low}$). Subsequently, a subset of the molecule is defined as a model fragment and it is isolated by using an atom (or a functional group) to keep the valence shell fully populated. $E_{Fragment\\ i}^{Low}$ is computed for the fragment using the same low-cost method as used initially for the whole system, and a high-cost method ($E_{Fragment\\ i}^{High}$). The difference in energy between the high-cost and low-cost methods ($E_{Fragment\\ i}^{High} - E_{Fragment\\ i}^{Low}$) is then added to our total energy. This way, we can interpret the ONIOM method as an iterative procedure where the error associated with our low-cost solver is removed.\n", "\n", @@ -120,7 +118,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -148,9 +146,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\t\tFragment Number : # 2 \n", + "\t\t------------------------\n", + "\t\t{'qubit_hamiltonian_terms': 27, 'circuit_width': 4, 'circuit_gates': 23, 'circuit_2qubit_gates': 8, 'circuit_var_gates': 3, 'vqe_variational_parameters': 3}\n", + "\n" + ] + } + ], "source": [ "resources = oniom_solver.get_resources()" ] @@ -164,11 +173,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": { "scrolled": true }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ONIOM Energy: -1278.214343118188\n", + "Hartree-Fock Energy: -1274.4936842154366\n" + ] + } + ], "source": [ "e_oniom = oniom_solver.simulate()\n", "e_hf = oniom_solver.fragments[0].mol_low.mf_energy\n", @@ -200,11 +218,34 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": { "scrolled": true }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "

" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], "source": [ "import matplotlib.pyplot as plt\n", "\n", @@ -244,9 +285,9 @@ ], "metadata": { "kernelspec": { - "display_name": "tangelo_sept_21", + "display_name": "qsdk", "language": "python", - "name": "tangelo_sept_21" + "name": "qsdk" }, "language_info": { "codemirror_mode": { @@ -258,7 +299,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.0" + "version": "3.9.9" } }, "nbformat": 4, diff --git a/examples/qemist_cloud_hardware_experiments_braket.ipynb b/examples/qemist_cloud_hardware_experiments_braket.ipynb index 0e54fa2eb..8728923ad 100755 --- a/examples/qemist_cloud_hardware_experiments_braket.ipynb +++ b/examples/qemist_cloud_hardware_experiments_braket.ipynb @@ -30,6 +30,7 @@ "metadata": {}, "source": [ "In order to succesfully submit an experiment, you will first need to:\n", + "\n", "- install `Tangelo`\n", "- install `qemist-client` (QEMIST client library)\n", "\n", @@ -304,9 +305,9 @@ ], "metadata": { "kernelspec": { - "display_name": "tangelo_aug_21", + "display_name": "tangelo_docs_aesthetics", "language": "python", - "name": "tangelo_aug_21" + "name": "tangelo_docs_aesthetics" }, "language_info": { "codemirror_mode": { @@ -318,7 +319,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.0" + "version": "3.8.10" } }, "nbformat": 4, diff --git a/examples/vqe.ipynb b/examples/vqe.ipynb index 20a42f22e..2a9a4b832 100755 --- a/examples/vqe.ipynb +++ b/examples/vqe.ipynb @@ -18,12 +18,12 @@ "## Table of contents:\n", "* [1. Overview of VQE](#1)\n", "* [2. VQESolver class](#2)\n", - ">* [2.1 VQESolver instantiation](#21)\n", - ">* [2.2 VQESolver.build](#22)\n", - ">* [2.3 VQESolver.simulate](#23)\n", - ">* [2.4 VQESolver.energy_estimation](#24)\n", - ">* [2.5 VQESolver.get_resources](#25)\n", - ">* [2.6 VQESolver.get_rdm](#26)\n", + " * [2.1 VQESolver instantiation](#21)\n", + " * [2.2 VQESolver.build](#22)\n", + " * [2.3 VQESolver.simulate](#23)\n", + " * [2.4 VQESolver.energy_estimation](#24)\n", + " * [2.5 VQESolver.get_resources](#25)\n", + " * [2.6 VQESolver.get_rdm](#26)\n", "* [3. Option: frozen orbitals](#3)\n", "* [4. Option: ansatz and qubit mapping](#4)\n", "* [5. Option: classical optimizers and initial parameters](#5)\n", @@ -38,27 +38,27 @@ "\n", "The Variational Quantum Eigensolver (VQE) \\[[Peruzzo_et_al.,_2014](https://arxiv.org/abs/1304.3061), [McClean_et_al.,_2015](https://arxiv.org/abs/1509.04279)\\] has been introduced as a hybrid quantum–classical algorithm for simulating quantum systems. Some examples of quantum simulation using VQE include solving the molecular electronic Schrödinger equation and model systems in condensed matter physics (e.g., Fermi– and Bose–Hubbard models). In this notebook, we focus on VQE within the context of solving the molecular electronic structure problem for the ground-state energy of a molecular system. The second-quantized Hamiltonian of such a system, within the Born-Oppenheimer approximation, assumes the following form:\n", "\n", - "\\begin{equation}\n", + "$$\n", "\\hat{H} = h_{\\text{nuc}} + \\sum\\limits_{p,q} h^{p}_{q} a^{\\dagger}_p a_q + \\sum\\limits_{p,q,r,s} h^{pq}_{rs} a^{\\dagger}_p a^{\\dagger}_q a_s a_r\\nonumber\n", - "\\end{equation}\n", + "$$\n", "\n", "Here, $h_{\\text{nuc}}$ denotes the nuclear repulsion energy. In what follows below, we use the mean-field solution from Hartee-Fock (HF) self-consistent theory as our computational starting point. As such, the basis-orbitals and coefficients $h_p^q$, $h_{rs}^{pq}$, are evaluated in the basis of molecular orbitals from HF. The Hamiltonian is then transformed into the qubit basis (e.g., Jordan–Wigner, Bravyi–Kitaev). This means that it is expressed entirely in terms of operators acting on qubits:\n", "\n", - "\\begin{equation}\n", + "$$\n", "\\hat{H} = h_{\\text{nuc}} + \\sum\\limits_{\\substack{p \\\\ \\alpha}} h_{p}^{\\alpha} \\sigma_p^{\\alpha} + \\sum\\limits_{\\substack{p,q \\\\ \\alpha,\\beta}} h_{pq}^{\\alpha\\beta}\\sigma_p^{\\alpha}\\otimes\\sigma_{q}^{\\beta} + \\sum\\limits_{\\substack{p,q,r \\\\ \\alpha,\\beta,\\gamma}}h_{pqr}^{\\alpha\\beta\\gamma}\\sigma_p^{\\alpha}\\otimes\\sigma_{q}^{\\beta}\\otimes\\sigma_r^{\\gamma} + \\ldots \\nonumber\n", - "\\end{equation}\n", + "$$\n", "\n", "In this expression, the $\\sigma_p^\\alpha$ are Pauli matrices ($\\alpha \\in \\{x,y,z\\}$), acting on the $p$-th qubit. We now consider a trial wavefunction ansatz $\\vert \\Psi(\\vec{\\theta}) \\rangle = U(\\vec{\\theta}) \\vert 0 \\rangle$ that depends on $m$ parameters defining $\\vec{\\theta}=(\\theta_1, \\theta_2, \\ldots, \\theta_m)$, which enter a unitary operator that acts on the reference (i.e., mean-field) state $\\vert 0 \\rangle$. The variational principle dictates that we can minimize the expectation value of the Hamiltonian, \n", "\n", - "\\begin{equation}\n", + "$$\n", "E = \\min\\limits_{\\vec{\\theta}} \\frac{\\langle \\Psi(\\vec{\\theta}) \\vert \\hat{H} \\vert \\Psi(\\vec{\\theta}) \\rangle}{\\langle \\Psi(\\vec{\\theta}) \\vert \\Psi(\\vec{\\theta}) \\rangle} \\geq E_{\\text{gs}}\\nonumber\n", - "\\end{equation}\n", + "$$\n", "\n", "to determine the optimal set of variational parameters. The energy thus computed will be an upper bound to the true ground-state energy $E_{\\text{gs}}$. Once a suitable variational trial ansatz has been chosen (e.g., a unitary coupled-cluster ansatz, a heuristic ansatz), we must provide a suitable set of initial guess parameters. If our ansatz is defined in the formalism of second-quantization, we must also transform it into the qubit basis before proceeding. We must also apply other approximations (e.g., Trotter–Suzuki) to render it amenable for translation into a quantum circuit. The resulting qubit form of the ansatz can then be translated into a quantum circuit and, thus, able to be implemented on quantum hardware. Once the initial state has been prepared using a quantum circuit, energy measurements are performed using quantum hardware or an appropriate simulation tool. The energy value obtained is the sum of the measurements of the expectation values of each of the terms that contribute to the Hamiltonian (assuming the wavefunction has been normalized to unity):\n", "\n", - "\\begin{equation}\n", + "$$\n", "E = \\langle \\Psi(\\vec{\\theta}) \\vert \\hat{H} \\vert \\Psi(\\vec{\\theta}) \\rangle =\\langle\\hat{H}\\rangle = h_{\\text{nuc}} + \\sum_{\\substack{p \\\\ \\alpha}} h_{p}^{\\alpha} \\langle\\sigma_p^{\\alpha}\\rangle + \\sum_{\\substack{p,q \\\\ \\alpha,\\beta}} h_{pq}^{\\alpha\\beta}\\langle\\sigma_p^{\\alpha}\\otimes\\sigma_{q}^{\\beta}\\rangle + \\sum_{\\substack{p,q,r \\\\ \\alpha,\\beta,\\gamma}}h_{pqr}^{\\alpha\\beta\\gamma}\\langle\\sigma_p^{\\alpha}\\otimes\\sigma_{q}^{\\beta}\\otimes\\sigma_r^{\\gamma}\\rangle + \\ldots \\nonumber\n", - "\\end{equation}\n", + "$$\n", "\n", "The computed energy is then input to a classical optimizer in order to find a new set of variational parameters, which are then used to prepare a new state (i.e., a quantum circuit) on the quantum hardware. The process is repeated until convergence. The algorithm is illustrated below. " ] @@ -792,7 +792,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "> :warning Each ansatz works differently, or may not be compatible with all qubit mappings: we encourage you to check out the documentation. In particular, how the `Ansatz` class work and how to implement your own custom ansatz into VQE is covered in a separate notebook (`vqe_custom_ansatz.ipynb`).\n", + "> :warning: Each ansatz works differently, or may not be compatible with all qubit mappings. We encourage you to check out the documentation. In particular, how the `Ansatz` class work and how to implement your own custom ansatz into VQE is covered in a separate notebook (`vqe_custom_ansatz.ipynb`).\n", "\n", "We can also try using the restricted UCC ansatze UCC1 and UCC3 to solve this problem:" ] @@ -1003,9 +1003,9 @@ ], "metadata": { "kernelspec": { - "display_name": "Tangelo", + "display_name": "tangelo_docs_aesthetics", "language": "python", - "name": "tangelo" + "name": "tangelo_docs_aesthetics" }, "language_info": { "codemirror_mode": { @@ -1017,7 +1017,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.0" + "version": "3.8.10" } }, "nbformat": 4, From 0e363a96daf94450a00e3c7d7c0632e4d51f3045 Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Tue, 14 Dec 2021 18:20:37 +0100 Subject: [PATCH 22/68] test --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index b05faea30..2d64db1f4 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,5 @@ +[![GitHub License](https://img.shields.io/badge/License-Apache-green)](https://github.com/nasa/hybridq/blob/main/LICENSE) + Tangelo overview ============= From 83f8301b30621d57447fd90b8b8c74eaa8a43ae8 Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Tue, 14 Dec 2021 18:25:18 +0100 Subject: [PATCH 23/68] test --- README.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 2d64db1f4..5d9c8f696 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,6 @@ -[![GitHub License](https://img.shields.io/badge/License-Apache-green)](https://github.com/nasa/hybridq/blob/main/LICENSE) +[![GitHub License](https://img.shields.io/badge/License-Apache-green)](https://github.com/quantumsimulation//QEMIST_qSDK/main/LICENSE) + +.. |licence| image:: https://assets.readthedocs.org/static/projects/badges/passing-flat.svg Tangelo overview ============= From 57dc95e186f2962fb04e732c5c1695f0ce89973c Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Tue, 14 Dec 2021 18:26:40 +0100 Subject: [PATCH 24/68] test --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index 5d9c8f696..7c901996f 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,7 @@ [![GitHub License](https://img.shields.io/badge/License-Apache-green)](https://github.com/quantumsimulation//QEMIST_qSDK/main/LICENSE) +|licence| + .. |licence| image:: https://assets.readthedocs.org/static/projects/badges/passing-flat.svg Tangelo overview From 78e49c226360ef04ef3d820c6502149daac7c20d Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Tue, 14 Dec 2021 18:28:20 +0100 Subject: [PATCH 25/68] test --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 7c901996f..df62019d7 100644 --- a/README.rst +++ b/README.rst @@ -1,8 +1,8 @@ -[![GitHub License](https://img.shields.io/badge/License-Apache-green)](https://github.com/quantumsimulation//QEMIST_qSDK/main/LICENSE) +[![GitHub License](https://img.shields.io/badge/License-Apache-green)](https://github.com/quantumsimulation/QEMIST_qSDK/main/LICENSE) |licence| -.. |licence| image:: https://assets.readthedocs.org/static/projects/badges/passing-flat.svg +.. |licence| image:: https://img.shields.io/badge/License-Apache-green Tangelo overview ============= From 83ccbe36085d8f05a101967016ddece70fc73529 Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Tue, 14 Dec 2021 18:36:34 +0100 Subject: [PATCH 26/68] test --- README.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.rst b/README.rst index df62019d7..2c3ee2728 100644 --- a/README.rst +++ b/README.rst @@ -1,9 +1,14 @@ [![GitHub License](https://img.shields.io/badge/License-Apache-green)](https://github.com/quantumsimulation/QEMIST_qSDK/main/LICENSE) +/github/workflow/status//:repo/:workflow/:branch + |licence| +|build| .. |licence| image:: https://img.shields.io/badge/License-Apache-green +.. |build| image:: https://github.com/quantumsimulation/QEMIST_qSDK/actions/workflows/github_actions_automated_testing.yml/badge.svg + Tangelo overview ============= From 0ff59c0fa612189bd90e612806ec584ca85a8121 Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Tue, 14 Dec 2021 18:53:04 +0100 Subject: [PATCH 27/68] Update README.rst --- README.rst | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index 2c3ee2728..1d229c349 100644 --- a/README.rst +++ b/README.rst @@ -1,16 +1,20 @@ -[![GitHub License](https://img.shields.io/badge/License-Apache-green)](https://github.com/quantumsimulation/QEMIST_qSDK/main/LICENSE) +[![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/nasa/hybridq.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/nasa/hybridq/context:python) -/github/workflow/status//:repo/:workflow/:branch +.. |PyPI version shields.io| image:: https://img.shields.io/pypi/v/ansicolortags.svg + :target: https://pypi.python.org/pypi/ansicolortags/ +Tangelo overview +================ + +|maintainer| |licence| |build| +.. |maintainer| image:: https://img.shields.io/badge/maintainer-GoodChemistry-blue .. |licence| image:: https://img.shields.io/badge/License-Apache-green + :target: https://github.com/quantumsimulation/QEMIST_qSDK/blob/main/README.rst .. |build| image:: https://github.com/quantumsimulation/QEMIST_qSDK/actions/workflows/github_actions_automated_testing.yml/badge.svg - - -Tangelo overview -============= +.. |python| image:: Welcome ! @@ -84,7 +88,6 @@ Tutorials Please check the ``examples`` folder jupyter notebook tutorials and other examples. - Tests ----- From 2fc120faa4c6c4f876992c8e857d49ebcb58d419 Mon Sep 17 00:00:00 2001 From: JamesB-1qbit <84878946+JamesB-1qbit@users.noreply.github.com> Date: Tue, 14 Dec 2021 19:28:08 -0500 Subject: [PATCH 28/68] JKMN mapping implementation (#95) * jkmn mapping now integrated, reconfigured vaccuum state call --- .../variational/tests/test_vqe_solver.py | 18 +- tangelo/toolboxes/ansatz_generator/hea.py | 5 +- tangelo/toolboxes/qubit_mappings/__init__.py | 1 + tangelo/toolboxes/qubit_mappings/jkmn.py | 179 ++++++++++++++++++ .../qubit_mappings/mapping_transform.py | 14 +- .../qubit_mappings/statevector_mapping.py | 30 ++- .../tests/test_mapping_transform.py | 43 ++++- .../tests/test_statevector_mapping.py | 27 +++ 8 files changed, 298 insertions(+), 19 deletions(-) create mode 100644 tangelo/toolboxes/qubit_mappings/jkmn.py diff --git a/tangelo/algorithms/variational/tests/test_vqe_solver.py b/tangelo/algorithms/variational/tests/test_vqe_solver.py index e6cc6d4c2..c3aaedc09 100644 --- a/tangelo/algorithms/variational/tests/test_vqe_solver.py +++ b/tangelo/algorithms/variational/tests/test_vqe_solver.py @@ -49,8 +49,8 @@ def test_get_resources_h2_mappings(self): """Resource estimation, with UCCSD ansatz, given initial parameters. Each of JW, BK, and scBK mappings are checked. """ - mappings = ["jw", "bk", "scbk"] - expected_values = [(15, 4), (15, 4), (5, 2)] + mappings = ["jw", "bk", "scbk", "jkmn"] + expected_values = [(15, 4), (15, 4), (5, 2), (15, 4)] vqe_options = {"molecule": mol_H2_sto3g, "ansatz": BuiltInAnsatze.UCCSD, "qubit_mapping": "jw", "initial_var_params": [0.1, 0.1]} @@ -327,6 +327,20 @@ def test_mapping_scBK(self): energy_target = -1.137270 self.assertAlmostEqual(energy, energy_target, places=5) + def test_mapping_jkmn(self): + """Test that JKMN mapping recovers the expected result, to within + 1e-6 Ha, for the example of H2 and MP2 initial guess. + """ + vqe_options = {"molecule": mol_H2_sto3g, "ansatz": BuiltInAnsatze.UCCSD, "initial_var_params": "MP2", "verbose": False, + "qubit_mapping": "jkmn"} + + vqe_solver = VQESolver(vqe_options) + vqe_solver.build() + energy = vqe_solver.simulate() + + energy_target = -1.137270 + self.assertAlmostEqual(energy, energy_target, places=5) + def test_spin_reorder_equivalence(self): """Test that re-ordered spin input (all up followed by all down) returns the same optimized energy result for both JW and BK mappings. diff --git a/tangelo/toolboxes/ansatz_generator/hea.py b/tangelo/toolboxes/ansatz_generator/hea.py index c5fb4f2b3..0915b216c 100644 --- a/tangelo/toolboxes/ansatz_generator/hea.py +++ b/tangelo/toolboxes/ansatz_generator/hea.py @@ -129,7 +129,10 @@ def prepare_reference_state(self): mapping=self.qubit_mapping, up_then_down=self.up_then_down) elif self.reference_state == "zero": - return Circuit() + return get_reference_circuit(n_spinorbitals=self.n_qubits, + n_electrons=0, + mapping=self.qubit_mapping, + up_then_down=self.up_then_down) def build_circuit(self, var_params=None): """Construct the variational circuit to be used as our ansatz.""" diff --git a/tangelo/toolboxes/qubit_mappings/__init__.py b/tangelo/toolboxes/qubit_mappings/__init__.py index fa0378cf8..77a714c32 100644 --- a/tangelo/toolboxes/qubit_mappings/__init__.py +++ b/tangelo/toolboxes/qubit_mappings/__init__.py @@ -15,3 +15,4 @@ from .jordan_wigner import jordan_wigner from .bravyi_kitaev import bravyi_kitaev from .symmetry_conserving_bravyi_kitaev import symmetry_conserving_bravyi_kitaev +from .jkmn import jkmn diff --git a/tangelo/toolboxes/qubit_mappings/jkmn.py b/tangelo/toolboxes/qubit_mappings/jkmn.py new file mode 100644 index 000000000..64c84a241 --- /dev/null +++ b/tangelo/toolboxes/qubit_mappings/jkmn.py @@ -0,0 +1,179 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import numpy as np +from openfermion.transforms.opconversions.conversions import get_majorana_operator + +from tangelo.toolboxes.operators import QubitOperator +from tangelo.linq import Circuit, Gate + +sigma_map = {"0": "X", "1": "Y", "2": "Z"} + + +def _node_value(p, l): + """Obtain the node value from vector p and depth l as given in Eq (3) in + arXiv:1910.10746v2""" + nv = (3**l - 1)//2 + for j in range(l): + nv += 3**(l-1-j)*int(p[j]) + return nv + + +def _jkmn_list(h): + """Generate the full list of paths in the JKMN defined ternary tree of height h + + The algorithm is described in arXiv:1910.10746v2 + + Args: + h (int) : The height of the full ternary tree to generate the leaf paths + + Returns: + list : The list with elements that generate the qubit operators + for each path in the ternary tree + """ + t_list = list() + for i in range(3**h): + # string representation of leaf index with base 3 and padding 0s to the tree height h + terstring = np.base_repr(i, base=3).rjust(h, '0') + + # descend ternary tree and obtain tuples describing QubitOperator + c_qu_op = [] + for ch in range(h): + # generates tuple that initiates QubitOperator on index nv and operator op=X,Y,Z + nv = _node_value(terstring, ch) + op = sigma_map[terstring[ch]] + c_qu_op += [(nv, op)] + t_list += [c_qu_op] + return t_list + + +def _jkmn_dict(n_qubits): + """Generate the mapping from Majorana modes to qubit operators + + The algorithm is described in arXiv:1910.10746v2 + + Args: + n_qubits (int) : The number of qubits in the system + + Returns: + dict : The mapping from Majorana modes to qubit operators + """ + + # Calculate initial height of ternary tree (largest full tree with n < n_qubit nodes) + h = int(np.log10(2 * n_qubits + 1) / np.log10(3)) + + # create full tree of largest size with less than n nodes + all_list = _jkmn_list(h) + + # Obtain number of leaves of tree that need to be branched + n_leaves_to_qubit = n_qubits - (3**h - 1) // 2 + + # change first n_leaves_to_qubit leaves to qubits + prepend_list = [] + for j in range(n_leaves_to_qubit): + # remove leaf and change to node append sigma_x sigma_y sigma_z + item_to_qubit = all_list.pop(0) + for i in range(3): + nv = (3**(h)-1)//2 + j + prepend_list.append(item_to_qubit+[(nv, sigma_map[str(i)])]) + + all_list = prepend_list + all_list + + # Create map from Majorana modes to QubitOperators ignoring furthest right leaf + full_map = dict() + for i, v in enumerate(all_list[:-1]): + full_map[i] = QubitOperator(v) + return full_map + + +def jkmn(fermion_operator, n_qubits): + """The JKMN mapping of a fermion operator as described in arXiv:1910.10746v2 + + Args: + fermion_operator (FermionOperator) : The fermion operator to transform to a qubit operator + n_qubits (int) : The number of qubits in the system + + Returns: + QubitOperator : The qubit operator corresponding to the Fermion Operator + """ + mj_op = get_majorana_operator(fermion_operator) + jkmn_map = _jkmn_dict(n_qubits) + + full_operator = QubitOperator() + for term, coeff in mj_op.terms.items(): + if abs(coeff) > 1.e-12: + c_term = QubitOperator([], coeff) + for mj in term: + c_term *= jkmn_map[mj] + full_operator += c_term + full_operator.compress() + return full_operator + + +def jkmn_vaccuum_circuit(n_qubits, jkmn_map=None): + """Generate the circuit that initializes vaccuum state for JKMN mapping + + Args: + n_qubits (int) : The number of qubits in the system + jkmn_map (dict) : The mapping from a Majorana operator index to a QubitOperator + + Returns: + Circuit : The circuit that prepares the vaccuum state. + """ + + if jkmn_map is None: + jkmn_map = _jkmn_dict(n_qubits) + fock_dict = dict() + rot_dict = dict() + # generate number operators + for i in range(n_qubits): + fock_dict[i] = 0.5 * QubitOperator([]) - 0.5j * jkmn_map[2*i] * jkmn_map[2*i+1] + for k, v in fock_dict[i].terms.items(): + for i in k: + if i[0] not in rot_dict: + rot_dict[i[0]] = i[1] + gate_list = [] + for k, v in rot_dict.items(): + if v == "X": + gate_list.append(Gate("H", int(k))) + return Circuit(gate_list, n_qubits=n_qubits) + + +def jkmn_prep_circuit(vector): + r"""Generate the circuit corresponding to a HF state with occupations defined by given occupation vector + + The vaccuum state preparation circuit is obtained. Each fermionic mode i is then generated by applying + \gamma_{2*i} = a_i^{\dagger} + a_i + + Args: + vector (list of int) : The occupation of each spinorbital + + Returns: + Circuit : The state preparation circuit for a HF state with occupation given by vector + """ + n_qubits = len(vector) + jkmn_map = _jkmn_dict(n_qubits) + vacuum_circuit = jkmn_vaccuum_circuit(n_qubits, jkmn_map) + + state_prep_qu_op = QubitOperator([], 1) + for i, occ in enumerate(vector): + if occ == 1: + state_prep_qu_op *= jkmn_map[2*i] + + gate_list = [] + for k, v in state_prep_qu_op.terms.items(): + for i in k: + gate_list.append(Gate(i[1], i[0])) + return vacuum_circuit + Circuit(gate_list) diff --git a/tangelo/toolboxes/qubit_mappings/mapping_transform.py b/tangelo/toolboxes/qubit_mappings/mapping_transform.py index 4fe59bf59..7d51f9e68 100644 --- a/tangelo/toolboxes/qubit_mappings/mapping_transform.py +++ b/tangelo/toolboxes/qubit_mappings/mapping_transform.py @@ -20,14 +20,15 @@ """ +import warnings import numpy as np from collections.abc import Iterable from openfermion import FermionOperator as ofFermionOperator from tangelo.toolboxes.operators import FermionOperator, QubitOperator -from tangelo.toolboxes.qubit_mappings import jordan_wigner, bravyi_kitaev, symmetry_conserving_bravyi_kitaev +from tangelo.toolboxes.qubit_mappings import jordan_wigner, bravyi_kitaev, symmetry_conserving_bravyi_kitaev, jkmn -available_mappings = {"JW", "BK", "SCBK"} +available_mappings = {"JW", "BK", "SCBK", "JKMN"} def get_qubit_number(mapping, n_spinorbitals): @@ -105,7 +106,10 @@ def fermion_to_qubit_mapping(fermion_operator, mapping, n_spinorbitals=None, n_e if up_then_down: if n_spinorbitals is None: raise ValueError("The number of spin-orbitals is required to execute basis re-ordering.") - fermion_operator = make_up_then_down(fermion_operator, n_spinorbitals=n_spinorbitals) + if mapping.upper() == "JKMN": + warnings.warn("Tangelo implementation of JKMN only supports up_then_down=False, ignoring up_then_down=True") + else: + fermion_operator = make_up_then_down(fermion_operator, n_spinorbitals=n_spinorbitals) if mapping.upper() == "JW": qubit_operator = jordan_wigner(fermion_operator) @@ -124,6 +128,10 @@ def fermion_to_qubit_mapping(fermion_operator, mapping, n_spinorbitals=None, n_e n_electrons=n_electrons, up_then_down=up_then_down, spin=spin) + elif mapping.upper() == "JKMN": + if n_spinorbitals is None: + raise ValueError("JKMN mapping requires specification of number of qubits") + qubit_operator = jkmn(fermion_operator, n_qubits=n_spinorbitals) converted_qubit_op = QubitOperator() converted_qubit_op.terms = qubit_operator.terms.copy() diff --git a/tangelo/toolboxes/qubit_mappings/statevector_mapping.py b/tangelo/toolboxes/qubit_mappings/statevector_mapping.py index 28bc418a3..837c0547b 100644 --- a/tangelo/toolboxes/qubit_mappings/statevector_mapping.py +++ b/tangelo/toolboxes/qubit_mappings/statevector_mapping.py @@ -21,10 +21,11 @@ import warnings from tangelo.linq import Gate, Circuit +from tangelo.toolboxes.qubit_mappings.jkmn import jkmn_prep_circuit from openfermion.transforms import bravyi_kitaev_code -available_mappings = {"JW", "BK", "SCBK"} +available_mappings = {"JW", "BK", "SCBK", "JKMN"} def get_vector(n_spinorbitals, n_electrons, mapping, up_then_down=False, spin=None): @@ -59,7 +60,10 @@ def get_vector(n_spinorbitals, n_electrons, mapping, up_then_down=False, spin=No else: vector[:n_electrons] = 1 if up_then_down: - vector = np.concatenate((vector[::2], vector[1::2])) + if mapping.upper() == "JKMN": + warnings.warn("Tangelo implementation of JKMN only supports up_then_down=False, ignoring up_then_down=True") + else: + vector = np.concatenate((vector[::2], vector[1::2])) if mapping.upper() == "JW": return vector @@ -70,6 +74,8 @@ def get_vector(n_spinorbitals, n_electrons, mapping, up_then_down=False, spin=No warnings.warn("Symmetry-conserving Bravyi-Kitaev enforces all spin-up followed by all spin-down ordering.", RuntimeWarning) vector = np.concatenate((vector[::2], vector[1::2])) return do_scbk_transform(vector, n_spinorbitals) + elif mapping.upper() == "JKMN": + return vector def do_bk_transform(vector): @@ -104,24 +110,28 @@ def do_scbk_transform(vector, n_spinorbitals): return vector -def vector_to_circuit(vector): +def vector_to_circuit(vector, mapping=None): """Translate occupation vector into a circuit. Each occupied state corresponds to an X-gate on the associated qubit index. Args: vector (numpy array of int): occupation vector. + mapping (str) : qubit mapping that defines circuit preparation. Returns: Circuit: instance of tangelo.linq Circuit class. """ - n_qubits = len(vector) - circuit = Circuit(n_qubits=n_qubits) + if mapping is not None and mapping.upper() == "JKMN": + return jkmn_prep_circuit(vector) + else: + n_qubits = len(vector) + circuit = Circuit(n_qubits=n_qubits) - for index, occupation in enumerate(vector): - if occupation: - gate = Gate("X", target=index) - circuit.add_gate(gate) + for index, occupation in enumerate(vector): + if occupation: + gate = Gate("X", target=index) + circuit.add_gate(gate) return circuit @@ -144,5 +154,5 @@ def get_reference_circuit(n_spinorbitals, n_electrons, mapping, up_then_down=Fal Circuit: instance of tangelo.linq Circuit class. """ vector = get_vector(n_spinorbitals, n_electrons, mapping, up_then_down=up_then_down, spin=spin) - circuit = vector_to_circuit(vector) + circuit = vector_to_circuit(vector, mapping) return circuit diff --git a/tangelo/toolboxes/qubit_mappings/tests/test_mapping_transform.py b/tangelo/toolboxes/qubit_mappings/tests/test_mapping_transform.py index 964489acf..2cdc812d0 100644 --- a/tangelo/toolboxes/qubit_mappings/tests/test_mapping_transform.py +++ b/tangelo/toolboxes/qubit_mappings/tests/test_mapping_transform.py @@ -70,6 +70,22 @@ def test_scbk(self): qubit = fermion_to_qubit_mapping(fermion, mapping="SCBK", n_spinorbitals=4, n_electrons=2) self.assertEqual(qubit, scbk_operator) + def test_jkmn(self): + """Check output from JKMN transform""" + jkmn_operator = QubitOperator(((0, "Y"), (1, "X"), (3, "X")), -0.125j) + jkmn_operator += QubitOperator(((0, "Y"), (1, "X"), (3, "Y")), 0.125) + jkmn_operator += QubitOperator(((0, "Y"), (1, "Y"), (3, "X")), -0.125) + jkmn_operator += QubitOperator(((0, "Y"), (1, "Y"), (3, "Y")), -0.125j) + jkmn_operator += QubitOperator(((0, "Z"), (1, "Z"), (2, "Y")), 0.25j) + jkmn_operator += QubitOperator(((0, "Z"), (1, "Z"), (2, "Z")), 0.25) + jkmn_operator += QubitOperator(((2, "Y")), -0.25j) + jkmn_operator += QubitOperator(((2, "Z")), -0.25) + + fermion = FermionOperator(((1, 0), (2, 1)), 1.0) + FermionOperator(((0, 1), (3, 0)), 0.5) + + qubit = fermion_to_qubit_mapping(fermion, mapping="JKMN", n_spinorbitals=4) + self.assertEqual(qubit, jkmn_operator) + def test_scbk_invalid(self): """Check if fermion operator fails to conserve number parity or spin parity. In either case, scBK is not an appropriate mapping. @@ -99,14 +115,17 @@ def test_eigen(self): jw_operator = fermion_to_qubit_mapping(fermion, mapping="JW") bk_operator = fermion_to_qubit_mapping(fermion, mapping="BK", n_spinorbitals=4) scbk_operator = fermion_to_qubit_mapping(fermion, mapping="SCBK", n_spinorbitals=4, n_electrons=2) + jkmn_operator = fermion_to_qubit_mapping(fermion, mapping="JKMN", n_spinorbitals=4) jw_ground = np.linalg.eigvalsh(qubit_operator_sparse(jw_operator).todense()).min() bk_ground = np.linalg.eigvalsh(qubit_operator_sparse(bk_operator, n_qubits=4).todense()).min() scbk_ground = np.linalg.eigvalsh(qubit_operator_sparse(scbk_operator, n_qubits=2).todense()).min() + jkmn_ground = np.linalg.eigvalsh(qubit_operator_sparse(jkmn_operator, n_qubits=4).todense()).min() - self.assertEqual(ground, jw_ground) - self.assertEqual(ground, bk_ground) - self.assertEqual(ground, scbk_ground) + self.assertAlmostEqual(ground, jw_ground, places=15) + self.assertAlmostEqual(ground, bk_ground, places=15) + self.assertAlmostEqual(ground, scbk_ground, places=15) + self.assertAlmostEqual(ground, jkmn_ground, places=15) def test_spin_order(self): """Test that re-ordering of spin-orbitals from alternating up down to @@ -154,6 +173,24 @@ def test_scbk_reorder(self): up_then_down=False) self.assertEqual(scBK_reordered, scBK_notreordered) + def test_jkmn_reorder(self): + """Tangelo implementation of JKMN forces spin-orbitals to order alternately. Check that + the qubit Hamiltonian returned is the same whether the user passes a + FermionOperator with this ordering, or not. + """ + fermion = FermionOperator(((2, 0), (0, 1)), 1.) + FermionOperator(((0, 0), (2, 1)), -1.) + scBK_reordered = fermion_to_qubit_mapping(fermion_operator=fermion, + mapping="JKMN", + n_spinorbitals=4, + n_electrons=2, + up_then_down=True) + scBK_notreordered = fermion_to_qubit_mapping(fermion_operator=fermion, + mapping="JKMN", + n_spinorbitals=4, + n_electrons=2, + up_then_down=False) + self.assertEqual(scBK_reordered, scBK_notreordered) + if __name__ == "__main__": unittest.main() diff --git a/tangelo/toolboxes/qubit_mappings/tests/test_statevector_mapping.py b/tangelo/toolboxes/qubit_mappings/tests/test_statevector_mapping.py index dec60c97d..b2240815e 100644 --- a/tangelo/toolboxes/qubit_mappings/tests/test_statevector_mapping.py +++ b/tangelo/toolboxes/qubit_mappings/tests/test_statevector_mapping.py @@ -84,6 +84,12 @@ def test_all_same_energy_mol_H4_sto3g(self): mol_H4_sto3g.n_active_sos, mol_H4_sto3g.n_active_electrons, up_then_down=True) + qu_op_jkmn = fermion_to_qubit_mapping(ferm_op, + "JKMN", + mol_H4_sto3g.n_active_sos, + mol_H4_sto3g.n_active_electrons, + up_then_down=True) + # Test for spin 0, 2, and 4 for spin in range(3): vector_bk = get_vector(mol_H4_sto3g.n_active_sos, @@ -101,9 +107,15 @@ def test_all_same_energy_mol_H4_sto3g(self): mapping="JW", up_then_down=True, spin=spin*2) + vector_jkmn = get_vector(mol_H4_sto3g.n_active_sos, + mol_H4_sto3g.n_active_electrons, + mapping="JKMN", + up_then_down=True, + spin=spin*2) circuit_bk = vector_to_circuit(vector_bk) circuit_scbk = vector_to_circuit(vector_scbk) circuit_jw = vector_to_circuit(vector_jw) + circuit_jkmn = vector_to_circuit(vector_jkmn, "JKMN") qu_op_scbk = fermion_to_qubit_mapping(ferm_op, 'SCBK', @@ -115,8 +127,10 @@ def test_all_same_energy_mol_H4_sto3g(self): e_bk = sim.get_expectation_value(qu_op_bk, circuit_bk) e_scbk = sim.get_expectation_value(qu_op_scbk, circuit_scbk) e_jw = sim.get_expectation_value(qu_op_jw, circuit_jw) + e_jkmn = sim.get_expectation_value(qu_op_jkmn, circuit_jkmn) self.assertAlmostEqual(e_bk, e_jw, places=7, msg=f"Failed for bk vs jw for spin={spin}") self.assertAlmostEqual(e_jw, e_scbk, places=7, msg=f"Failed for jw vs scbk for spin={spin}") + self.assertAlmostEqual(e_scbk, e_jkmn, places=7, msg=f"Failed for jkmn vs scbk for spin={spin}") def test_all_same_energy_mol_H4_cation_sto3g(self): """Check that all mappings return statevectors that have the same energy expectation @@ -132,6 +146,11 @@ def test_all_same_energy_mol_H4_cation_sto3g(self): mol_H4_cation_sto3g.n_active_sos, mol_H4_cation_sto3g.n_active_electrons, up_then_down=True) + qu_op_jkmn = fermion_to_qubit_mapping(ferm_op, + "JKMN", + mol_H4_cation_sto3g.n_active_sos, + mol_H4_cation_sto3g.n_active_electrons, + up_then_down=True) # Test for spin 1 and 3 for spin in range(2): vector_bk = get_vector(mol_H4_cation_sto3g.n_active_sos, @@ -149,9 +168,15 @@ def test_all_same_energy_mol_H4_cation_sto3g(self): mapping="JW", up_then_down=True, spin=spin*2+1) + vector_jkmn = get_vector(mol_H4_cation_sto3g.n_active_sos, + mol_H4_cation_sto3g.n_active_electrons, + mapping="JKMN", + up_then_down=True, + spin=spin*2+1) circuit_bk = vector_to_circuit(vector_bk) circuit_scbk = vector_to_circuit(vector_scbk) circuit_jw = vector_to_circuit(vector_jw) + circuit_jkmn = vector_to_circuit(vector_jkmn, "JKMN") qu_op_scbk = fermion_to_qubit_mapping(ferm_op, 'SCBK', @@ -163,8 +188,10 @@ def test_all_same_energy_mol_H4_cation_sto3g(self): e_bk = sim.get_expectation_value(qu_op_bk, circuit_bk) e_scbk = sim.get_expectation_value(qu_op_scbk, circuit_scbk) e_jw = sim.get_expectation_value(qu_op_jw, circuit_jw) + e_jkmn = sim.get_expectation_value(qu_op_jkmn, circuit_jkmn) self.assertAlmostEqual(e_bk, e_jw, places=7, msg=f"Failed for bk vs jw for spin={spin}") self.assertAlmostEqual(e_jw, e_scbk, places=7, msg=f"Failed for jw vs scbk for spin={spin}") + self.assertAlmostEqual(e_scbk, e_jkmn, places=7, msg=f"Failed for scbk vs jkmn for spin={spin}") if __name__ == "__main__": From af2da55196b61499bce3fd18ae588bfdbcf1dd3a Mon Sep 17 00:00:00 2001 From: JamesB-1qbit <84878946+JamesB-1qbit@users.noreply.github.com> Date: Wed, 15 Dec 2021 13:45:25 -0500 Subject: [PATCH 29/68] added inverse function to Circuit (#78) * Inverse function for Gate and Circuit class * Added phase gate support to allow T,S inverse * added invertible_gates list --- tangelo/linq/circuit.py | 11 ++++++ tangelo/linq/gate.py | 35 +++++++++++++++++-- tangelo/linq/tests/test_circuits.py | 17 +++++++++ tangelo/linq/tests/test_gates.py | 19 ++++++++++ tangelo/linq/tests/test_translator.py | 18 ++++++++++ .../linq/translator/translate_json_ionq.py | 14 +++++++- tangelo/linq/translator/translate_openqasm.py | 4 ++- tangelo/linq/translator/translate_projectq.py | 5 +-- 8 files changed, 117 insertions(+), 6 deletions(-) diff --git a/tangelo/linq/circuit.py b/tangelo/linq/circuit.py index 743fbbf8b..f4c904c4c 100644 --- a/tangelo/linq/circuit.py +++ b/tangelo/linq/circuit.py @@ -130,5 +130,16 @@ def check_index_valid(index): # Keep track of the total gate count self._gate_counts[gate.name] = self._gate_counts.get(gate.name, 0) + 1 + def inverse(self): + """Return the inverse (adjoint) of a circuit + + This is performed by reversing the Gate order and applying the inverse to each Gate. + + Returns: + Circuit: the inverted circuit + """ + gate_list = [gate.inverse() for gate in reversed(self._gates)] + return Circuit(gate_list) + def serialize(self): return {"type": "QuantumCircuit", "gates": [gate.serialize() for gate in self._gates]} diff --git a/tangelo/linq/gate.py b/tangelo/linq/gate.py index 41aa0c4ae..3da873183 100644 --- a/tangelo/linq/gate.py +++ b/tangelo/linq/gate.py @@ -16,15 +16,18 @@ gate operation, without tying it to a particular backend or an underlying mathematical operation. """ - +from math import pi from typing import Union -from numpy import integer, ndarray +from numpy import integer, ndarray, floating ONE_QUBIT_GATES = {"H", "X", "Y", "Z", "S", "T", "RX", "RY", "RZ", "PHASE"} TWO_QUBIT_GATES = {"CNOT", "CX", "CY", "CZ", "CRX", "CRY", "CRZ", "CPHASE", "XX", "SWAP"} THREE_QUBIT_GATES = {"CSWAP"} PARAMETERIZED_GATES = {"RX", "RY", "RZ", "PHASE", "CRX", "CRY", "CRZ", "CPHASE", "XX"} +INVERTIBLE_GATES = {"H", "X", "Y", "Z", "S", "T", "RX", "RY", "RZ", "CH", "PHASE", + "CNOT", "CX", "CY", "CZ", "CRX", "CRY", "CRZ", "CPHASE", "XX", "SWAP" + "CSWAP"} class Gate(dict): @@ -100,6 +103,34 @@ def __str__(self): return mystr + def inverse(self): + """Returns the inverse (adjoint) of a gate. + + Return: + Gate: the inverse of the gate. + """ + if self.name not in INVERTIBLE_GATES: + raise AttributeError(f"{self.gate} is not an invertible gate") + if self.parameter == "": + new_parameter = "" + elif isinstance(self.parameter, (float, floating, int, integer)): + new_parameter = -self.parameter + elif self.name in {"T", "S"}: + new_parameter = -pi / 2 if self.name == "T" else -pi / 4 + return Gate(name="PHASE", + target=self.target, + control=self.control, + parameter=new_parameter, + is_variational=self.is_variational) + + else: + raise AttributeError(f"{self.name} is not an invertible gate when parameter is {self.parameter}") + return Gate(name=self.name, + target=self.target, + control=self.control, + parameter=new_parameter, + is_variational=self.is_variational) + def serialize(self): return {"type": "Gate", "params": {"name": self.name, "target": self.target, diff --git a/tangelo/linq/tests/test_circuits.py b/tangelo/linq/tests/test_circuits.py index 5b5845d78..70cdf00f5 100644 --- a/tangelo/linq/tests/test_circuits.py +++ b/tangelo/linq/tests/test_circuits.py @@ -19,6 +19,7 @@ import unittest import copy +from math import pi from collections import Counter from tangelo.linq import Gate, Circuit @@ -98,6 +99,22 @@ def test_fixed_sized_circuit_below(self): circuit_fixed = Circuit([Gate("H", 0)], n_qubits=n_qubits) self.assertTrue(circuit_fixed.width == n_qubits) + def test_inverse(self): + """ Test if inverse function returns the proper set of gates by comparing print strings.""" + + mygates_inverse = list() + mygates_inverse.append(Gate("RX", 1, parameter=-2.)) + mygates_inverse.append(Gate("Y", 0)) + mygates_inverse.append(Gate("CNOT", 2, control=1)) + mygates_inverse.append(Gate("CNOT", 1, control=0)) + mygates_inverse.append(Gate("H", 2)) + circuit1_inverse = Circuit(mygates_inverse) + self.assertTrue(circuit1.inverse().__str__(), circuit1_inverse.__str__()) + + ts_circuit = Circuit([Gate("T", 0), Gate("S", 1)]) + ts_circuit_inverse = Circuit([Gate("PHASE", 0, parameter=-pi/4), Gate("PHASE", 0, parameter=-pi/2)]) + self.assertTrue(ts_circuit.inverse().__str__(), ts_circuit_inverse.__str__()) + if __name__ == "__main__": unittest.main() diff --git a/tangelo/linq/tests/test_gates.py b/tangelo/linq/tests/test_gates.py index 2c826d312..415a67973 100644 --- a/tangelo/linq/tests/test_gates.py +++ b/tangelo/linq/tests/test_gates.py @@ -43,6 +43,25 @@ def test_some_gates(self): for gate in [H_gate, CNOT_gate, RX_gate, RZ_gate, CCCX_gate]: print(gate) + def test_some_gates_inverse(self): + """ Test that some basic gates can be inverted with a few different parameters, and fails when non-invertible + parameters are passed""" + + # Create a Hadamard gate acting on qubit 2 + H_gate = Gate("H", 2) + H_gate_inverse = Gate("H", 2) + self.assertEqual(H_gate.inverse().__str__(), H_gate_inverse.__str__()) + + # Create a parameterized rotation on qubit 1 with angle 2 radians + RX_gate = Gate("RX", 1, parameter=2.) + RX_gate_inverse = Gate("RX", 1, parameter=-2.) + self.assertEqual(RX_gate.inverse().__str__(), RX_gate_inverse.__str__()) + + # Create a parameterized rotation on qubit 1 , with an undefined angle, that will be variational + RZ_gate = Gate("RZ", 1, parameter="an expression", is_variational=True) + with self.assertRaises(AttributeError): + RZ_gate.inverse() + def test_incorrect_gate(self): """ Test to catch a gate with inputs that do not make sense """ diff --git a/tangelo/linq/tests/test_translator.py b/tangelo/linq/tests/test_translator.py index 70dbd7e92..fa884a0d1 100644 --- a/tangelo/linq/tests/test_translator.py +++ b/tangelo/linq/tests/test_translator.py @@ -423,6 +423,24 @@ def test_unsupported_gate(self): circ = Circuit([Gate("Potato", 0)]) self.assertRaises(ValueError, translator.translate_qiskit, circ) + def test_translate_ionq_inverse(self): + """ Test that inverse of T and S circuits for ionQ return Tdag and Sdag after translation """ + + # Generate [Gate("Tdag", 0), Gate("Sdag", 0)] equivalent + circ = Circuit([Gate("PHASE", 0, parameter=-np.pi/4), Gate("PHASE", 0, parameter=-np.pi/2)]) + # Hard-coded inverse + inverse_circ = Circuit([Gate("S", 0), Gate("T", 0)]) + + ionq_circ_inverse = translator.translate_json_ionq(circ.inverse()) + ionq_inverse_circ = translator.translate_json_ionq(inverse_circ) + ionq_circ = translator.translate_json_ionq(circ) + # Hard-coded circuit dictionary + ionq_circ_dict = {'qubits': 1, 'circuit': [{'gate': 'ti', 'target': 0}, {'gate': 'si', 'target': 0}]} + + # ionq uses a dictionary to store circuits, convert to str and compare + self.assertEqual(str(ionq_inverse_circ), str(ionq_circ_inverse)) + self.assertEqual(str(ionq_circ), str(ionq_circ_dict)) + if __name__ == "__main__": unittest.main() diff --git a/tangelo/linq/translator/translate_json_ionq.py b/tangelo/linq/translator/translate_json_ionq.py index 78c981682..1393b3ee5 100644 --- a/tangelo/linq/translator/translate_json_ionq.py +++ b/tangelo/linq/translator/translate_json_ionq.py @@ -21,6 +21,7 @@ - how the order and conventions for some of the inputs to the gate operations may also differ. """ +from math import pi, isclose def get_ionq_gates(): @@ -31,7 +32,7 @@ def get_ionq_gates(): """ GATE_JSON_IONQ = dict() - for name in {"H", "X", "Y", "Z", "S", "T", "RX", "RY", "RZ", "CNOT"}: + for name in {"H", "X", "Y", "Z", "S", "T", "RX", "RY", "RZ", "CNOT", "PHASE"}: GATE_JSON_IONQ[name] = name.lower() return GATE_JSON_IONQ @@ -57,6 +58,17 @@ def translate_json_ionq(source_circuit): json_gates.append({'gate': GATE_JSON_IONQ[gate.name], 'target': gate.target[0]}) elif gate.name in {"RX", "RY", "RZ"}: json_gates.append({'gate': GATE_JSON_IONQ[gate.name], 'target': gate.target[0], 'rotation': gate.parameter}) + elif gate.name in {"PHASE"}: + if isclose(gate.parameter, pi / 2, abs_tol=1.e-7): + json_gates.append({'gate': 's', 'target': gate.target[0]}) + elif isclose(gate.parameter, - pi / 2, abs_tol=1.e-7): + json_gates.append({'gate': 'si', 'target': gate.target[0]}) + elif isclose(gate.parameter, pi / 4, abs_tol=1.e-7): + json_gates.append({'gate': 't', 'target': gate.target[0]}) + elif isclose(gate.parameter, - pi / 4, abs_tol=1.e-7): + json_gates.append({'gate': 'ti', 'target': gate.target[0]}) + else: + raise ValueError(f"Only phases of pi/2, -pi/2, pi/4, -pi/4 are supported with JSON IonQ translation") elif gate.name in {"CNOT"}: json_gates.append({'gate': GATE_JSON_IONQ[gate.name], 'target': gate.target[0], 'control': gate.control[0]}) else: diff --git a/tangelo/linq/translator/translate_openqasm.py b/tangelo/linq/translator/translate_openqasm.py index ad58c1f86..a5cb97ad7 100644 --- a/tangelo/linq/translator/translate_openqasm.py +++ b/tangelo/linq/translator/translate_openqasm.py @@ -39,7 +39,7 @@ def get_openqasm_gates(): GATE_OPENQASM = dict() for name in {"H", "X", "Y", "Z", "S", "T"}: GATE_OPENQASM[name] = name.lower() - for name in {"RX", "RY", "RZ", "MEASURE"}: + for name in {"RX", "RY", "RZ", "MEASURE", "PHASE"}: GATE_OPENQASM[name] = name.lower() GATE_OPENQASM["CNOT"] = "cx" @@ -110,6 +110,8 @@ def parse_param(s): # TODO: Rethink the use of enums for gates to set the equality CX=CNOT and enable other refactoring elif gate_name in {"cx"}: gate = Gate("CNOT", qubit_indices[1], control=qubit_indices[0]) + elif gate_name in {"p"}: + gate = Gate("PHASE", qubit_indices[0], parameter=eval(str(parameters[0]))) else: raise ValueError(f"Gate '{gate_name}' not supported with openqasm translation") abs_circ.add_gate(gate) diff --git a/tangelo/linq/translator/translate_projectq.py b/tangelo/linq/translator/translate_projectq.py index ec8ebdd27..32739c334 100644 --- a/tangelo/linq/translator/translate_projectq.py +++ b/tangelo/linq/translator/translate_projectq.py @@ -38,6 +38,7 @@ def get_projectq_gates(): for name in {"RX", "RY", "RZ", "MEASURE"}: GATE_PROJECTQ[name] = name[0] + name[1:].lower() GATE_PROJECTQ["CNOT"] = "CX" + GATE_PROJECTQ["PHASE"] = "R" return GATE_PROJECTQ @@ -63,7 +64,7 @@ def translate_projectq(source_circuit): for gate in source_circuit._gates: if gate.name in {"H", "X", "Y", "Z", "S", "T", "MEASURE"}: projectq_circuit += f"{GATE_PROJECTQ[gate.name]} | Qureg[{gate.target[0]}]\n" - elif gate.name in {"RX", "RY", "RZ"}: + elif gate.name in {"RX", "RY", "RZ", "PHASE"}: projectq_circuit += f"{GATE_PROJECTQ[gate.name]}({gate.parameter}) | Qureg[{gate.target[0]}]\n" elif gate.name in {"CNOT"}: projectq_circuit += f"{GATE_PROJECTQ[gate.name]} | ( Qureg[{gate.control[0]}], Qureg[{gate.target[0]}] )\n" @@ -112,7 +113,7 @@ def _translate_projectq2abs(projectq_str): if gate_name in {"H", "X", "Y", "Z", "S", "T"}: gate = Gate(gate_mapping[gate_name], qubit_indices[0]) - elif gate_name in {"Rx", "Ry", "Rz"}: + elif gate_name in {"Rx", "Ry", "Rz", "PHASE"}: gate = Gate(gate_mapping[gate_name], qubit_indices[0], parameter=parameters[0]) # #TODO: Rethink the use of enums for gates to set the equality CX=CNOT and enable other refactoring elif gate_name in {"CX"}: From 29c4a4c75d75048b893a839e6ccdd427db83b45e Mon Sep 17 00:00:00 2001 From: JamesB-1qbit <84878946+JamesB-1qbit@users.noreply.github.com> Date: Wed, 15 Dec 2021 15:53:56 -0500 Subject: [PATCH 30/68] many small fixes, added codestyle tests (#96) * pycodestyle added to automated testing. Independent test available for users to lint locally. --- .../github_actions_automated_testing.yml | 12 +++++-- dev_tools/pycodestyle | 6 ++++ dev_tools/test_conformance.py | 14 ++++++++ tangelo/__init__.py | 4 +-- .../algorithms/classical/tests/__init__.py | 1 - .../algorithms/variational/tests/__init__.py | 1 - tangelo/algorithms/variational/vqe_solver.py | 3 +- tangelo/helpers/utils.py | 3 +- .../qpu_connection/qemist_cloud_connection.py | 2 +- tangelo/linq/tests/__init__.py | 1 - tangelo/linq/tests/test_circuits.py | 4 +-- tangelo/linq/tests/test_simulator.py | 6 ++-- tangelo/linq/tests/test_simulator_noisy.py | 2 +- tangelo/linq/tests/test_translator.py | 3 +- tangelo/molecule_library.py | 2 +- .../problem_decomposition/dmet/__init__.py | 1 - .../dmet/_helpers/dmet_bath.py | 28 +++++++++------- .../dmet/_helpers/dmet_fragment.py | 15 +++++---- .../dmet/_helpers/dmet_onerdm.py | 6 ++-- .../dmet/_helpers/dmet_orbitals.py | 11 ++++--- .../dmet/_helpers/dmet_scf.py | 5 +-- .../dmet/_helpers/dmet_scf_guess.py | 5 +-- .../dmet/dmet_problem_decomposition.py | 3 +- .../problem_decomposition/dmet/fragment.py | 2 +- .../electron_localization/iao_localization.py | 18 +++++++---- .../meta_lowdin_localization.py | 1 + .../problem_decomposition/oniom/__init__.py | 1 - .../oniom/_helpers/__init__.py | 1 - .../oniom/_helpers/helper_classes.py | 4 +-- .../oniom/oniom_problem_decomposition.py | 2 +- .../problem_decomposition/tests/__init__.py | 1 - .../tests/dmet/__init__.py | 1 - .../tests/dmet/test_dmet.py | 2 +- .../tests/dmet/test_dmet_oneshot_loop.py | 4 +-- .../tests/dmet/test_dmet_orbitals.py | 1 + .../tests/oniom/__init__.py | 1 - tangelo/toolboxes/__init__.py | 1 - .../toolboxes/ansatz_generator/__init__.py | 1 - .../ansatz_generator/_general_unitary_cc.py | 2 +- .../toolboxes/ansatz_generator/_qubit_cc.py | 10 +++--- .../toolboxes/ansatz_generator/_qubit_mf.py | 4 +-- .../ansatz_generator/adapt_ansatz.py | 2 +- .../ansatz_generator/fermionic_operators.py | 13 ++++---- tangelo/toolboxes/ansatz_generator/hea.py | 4 +-- .../ansatz_generator/penalty_terms.py | 10 +++--- tangelo/toolboxes/ansatz_generator/qcc.py | 32 +++++++++---------- tangelo/toolboxes/ansatz_generator/qmf.py | 20 ++++++------ tangelo/toolboxes/ansatz_generator/rucc.py | 4 +-- .../ansatz_generator/tests/__init__.py | 1 - .../ansatz_generator/tests/test_qcc.py | 2 +- .../ansatz_generator/tests/test_qmf.py | 10 +++--- .../ansatz_generator/tests/test_upccgsd.py | 4 +-- tangelo/toolboxes/ansatz_generator/uccsd.py | 4 +-- tangelo/toolboxes/ansatz_generator/upccgsd.py | 4 +-- .../ansatz_generator/variational_circuit.py | 4 +-- .../toolboxes/measurements/tests/__init__.py | 1 - .../molecular_computation/__init__.py | 1 - .../molecular_computation/molecule.py | 11 ++++--- .../molecular_computation/tests/__init__.py | 1 - tangelo/toolboxes/operators/tests/__init__.py | 1 - .../post_processing/tests/__init__.py | 1 - .../qubit_mappings/mapping_transform.py | 2 +- .../qubit_mappings/tests/__init__.py | 1 - .../tests/test_bravyi_kitaev.py | 9 +++--- 64 files changed, 186 insertions(+), 151 deletions(-) create mode 100644 dev_tools/pycodestyle create mode 100644 dev_tools/test_conformance.py diff --git a/.github/workflows/github_actions_automated_testing.yml b/.github/workflows/github_actions_automated_testing.yml index 66672c42a..f98404d45 100755 --- a/.github/workflows/github_actions_automated_testing.yml +++ b/.github/workflows/github_actions_automated_testing.yml @@ -25,6 +25,16 @@ jobs: pip install pytest-cov pip install jupyter + - name: Install pycodestyle + run: | + python -m pip install pycodestyle + + - name: pycodestyle tests + run: | + cd dev_tools + pytest --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html test_conformance.py + if: always() + - name: Install backends except qsharp/qdk run: | pip install qiskit @@ -60,5 +70,3 @@ jobs: cd examples pytest --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html test_notebooks.py if: always() - - diff --git a/dev_tools/pycodestyle b/dev_tools/pycodestyle new file mode 100644 index 000000000..7f54187fe --- /dev/null +++ b/dev_tools/pycodestyle @@ -0,0 +1,6 @@ +[pycodestyle] +count = False +exclude = *build* +ignore = E123,E126,E127,E128,E201,E202,E226,E241,W503,W504,W605,E741 +max-line-length = 160 +statistics = True \ No newline at end of file diff --git a/dev_tools/test_conformance.py b/dev_tools/test_conformance.py new file mode 100644 index 000000000..b908048ea --- /dev/null +++ b/dev_tools/test_conformance.py @@ -0,0 +1,14 @@ +import unittest +import pycodestyle + + +class TestCodeFormat(unittest.TestCase): + + def test_conformance(self): + style = pycodestyle.StyleGuide(quiet=False, config_file="pycodestyle") + result = style.check_files(["../tangelo"]) + self.assertEqual(result.total_errors, 0, "Found code style errors and warnings.") + + +if __name__ == "__main__": + unittest.main() diff --git a/tangelo/__init__.py b/tangelo/__init__.py index 67c8a4a00..fafdb742e 100644 --- a/tangelo/__init__.py +++ b/tangelo/__init__.py @@ -15,10 +15,10 @@ import warnings import numpy as np +from tangelo.toolboxes.molecular_computation.molecule import Molecule, SecondQuantizedMolecule + sup = np.testing.suppress_warnings() warnings.filterwarnings("ignore", message="Using default_file_mode other than 'r' is deprecated") warnings.filterwarnings("ignore", message="`np") warnings.filterwarnings("ignore", category=DeprecationWarning) sup.filter(np.core) - -from tangelo.toolboxes.molecular_computation.molecule import Molecule, SecondQuantizedMolecule diff --git a/tangelo/algorithms/classical/tests/__init__.py b/tangelo/algorithms/classical/tests/__init__.py index 81a799660..532746351 100644 --- a/tangelo/algorithms/classical/tests/__init__.py +++ b/tangelo/algorithms/classical/tests/__init__.py @@ -11,4 +11,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - diff --git a/tangelo/algorithms/variational/tests/__init__.py b/tangelo/algorithms/variational/tests/__init__.py index 81a799660..532746351 100644 --- a/tangelo/algorithms/variational/tests/__init__.py +++ b/tangelo/algorithms/variational/tests/__init__.py @@ -11,4 +11,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - diff --git a/tangelo/algorithms/variational/vqe_solver.py b/tangelo/algorithms/variational/vqe_solver.py index 3b6813218..5b955006a 100644 --- a/tangelo/algorithms/variational/vqe_solver.py +++ b/tangelo/algorithms/variational/vqe_solver.py @@ -296,7 +296,8 @@ def operator_expectation(self, operator, var_params=None, n_active_mos=None, n_a if self.molecule: n_active_mos = self.molecule.n_active_mos else: - raise KeyError("Must supply n_active_mos when a QubitHamiltonian has initialized VQESolver and requesting the expectation of 'N', 'Sz', or 'S^2'") + raise KeyError("Must supply n_active_mos when a QubitHamiltonian has initialized VQESolver" + " and requesting the expectation of 'N', 'Sz', or 'S^2'") if operator == "N": exp_op = number_operator(n_active_mos, up_then_down=False) elif operator == "Sz": diff --git a/tangelo/helpers/utils.py b/tangelo/helpers/utils.py index a04e7cc27..f2191134b 100644 --- a/tangelo/helpers/utils.py +++ b/tangelo/helpers/utils.py @@ -17,7 +17,8 @@ a default simulator. """ -import os, sys +import os +import sys class HiddenPrints: diff --git a/tangelo/linq/qpu_connection/qemist_cloud_connection.py b/tangelo/linq/qpu_connection/qemist_cloud_connection.py index f4ab4edd5..9dac4aaf2 100644 --- a/tangelo/linq/qpu_connection/qemist_cloud_connection.py +++ b/tangelo/linq/qpu_connection/qemist_cloud_connection.py @@ -109,7 +109,7 @@ def job_result(qemist_cloud_job_id): print(f"Reconnect and block until the problem is complete with " f"qemist_client.util.monitor_problem_status({qemist_cloud_job_id}).\n\n") - except: + except Exception: print(f"\n\nYour problem is still running with handle {qemist_cloud_job_id}.\n" f"Cancel the problem with qemist_client.util.cancel_problems({qemist_cloud_job_id}).\n" f"Reconnect and block until the problem is complete with qemist_client.util.monitor_problem_status({qemist_cloud_job_id}).\n\n") diff --git a/tangelo/linq/tests/__init__.py b/tangelo/linq/tests/__init__.py index 81a799660..532746351 100644 --- a/tangelo/linq/tests/__init__.py +++ b/tangelo/linq/tests/__init__.py @@ -11,4 +11,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - diff --git a/tangelo/linq/tests/test_circuits.py b/tangelo/linq/tests/test_circuits.py index 70cdf00f5..0c6c22ac8 100644 --- a/tangelo/linq/tests/test_circuits.py +++ b/tangelo/linq/tests/test_circuits.py @@ -56,8 +56,8 @@ def test_init(self): def test_is_variational(self): """ Ensure that the circuit is labeled as variational as soon as one variational gate is present """ - self.assertTrue(circuit1.is_variational == False) - self.assertTrue(circuit3.is_variational == True) + self.assertTrue(circuit1.is_variational is False) + self.assertTrue(circuit3.is_variational is True) def test_width(self): """ Ensure the width attribute of the circuit object (number of qubits) matches the gate operations diff --git a/tangelo/linq/tests/test_simulator.py b/tangelo/linq/tests/test_simulator.py index 1c6f6592d..c247d6164 100644 --- a/tangelo/linq/tests/test_simulator.py +++ b/tangelo/linq/tests/test_simulator.py @@ -223,7 +223,7 @@ def test_get_exp_value_from_statevector_h2(self): try: self.assertAlmostEqual(energy, expected, delta=1e-5) - except: + except AssertionError: test_fail = True print(f"{self._testMethodName} : Assertion failed {b} (result = {energy:.7f}, expected = {expected})") if test_fail: @@ -256,7 +256,7 @@ def test_get_exp_value_from_initial_statevector_h2(self): try: self.assertAlmostEqual(energy, expected, delta=1e-5) - except: + except AssertionError: test_fail = True print(f"{self._testMethodName} : Assertion failed {b} (result = {energy:.7f}, expected = {expected})") if test_fail: @@ -290,7 +290,7 @@ def test_get_exp_value_from_statevector_h4(self): try: self.assertAlmostEqual(energy, expected, delta=1e-5) - except: + except AssertionError: test_fail = True print(f"{self._testMethodName} : Assertion failed {b} (result = {energy:.7f}, expected = {expected})") if test_fail: diff --git a/tangelo/linq/tests/test_simulator_noisy.py b/tangelo/linq/tests/test_simulator_noisy.py index 8a788275f..145aed478 100644 --- a/tangelo/linq/tests/test_simulator_noisy.py +++ b/tangelo/linq/tests/test_simulator_noisy.py @@ -212,7 +212,7 @@ def test_get_expectation_value_noisy(self): noise = 0.00 nmp_no_noise.add_quantum_error("CNOT", "pauli", [noise, noise, noise]) sim_no_noise = Simulator(target=default_simulator, n_shots=10**6, noise_model=nmp_no_noise) - + # Small Noise model nmp_small_noise = NoiseModel() noise = 0.01 diff --git a/tangelo/linq/tests/test_translator.py b/tangelo/linq/tests/test_translator.py index fa884a0d1..ff15c474d 100644 --- a/tangelo/linq/tests/test_translator.py +++ b/tangelo/linq/tests/test_translator.py @@ -336,7 +336,8 @@ def test_abs2openqasm(self): This test failing implies that either Qiskit QASM output has changed or that translate_qiskit fails (the latter has its own tests) """ - openqasm_circuit1 = '''OPENQASM 2.0;\ninclude "qelib1.inc";\nqreg q[3];\ncreg c[3];\nh q[2];\ncx q[0],q[1];\ncx q[1],q[2];\ny q[0];\ns q[0];\nrx(1.5) q[1];\nmeasure q[0] -> c[0];\n''' + openqasm_circuit1 = '''OPENQASM 2.0;\ninclude "qelib1.inc";\nqreg q[3];\ncreg c[3];\nh q[2];\ncx q[0],q[1];\ncx '''\ + '''q[1],q[2];\ny q[0];\ns q[0];\nrx(1.5) q[1];\nmeasure q[0] -> c[0];\n''' openqasm_circuit2 = translator.translate_openqasm(abs_circ_mixed) print(openqasm_circuit2) diff --git a/tangelo/molecule_library.py b/tangelo/molecule_library.py index 31e32287a..9b4ddc1ae 100644 --- a/tangelo/molecule_library.py +++ b/tangelo/molecule_library.py @@ -78,7 +78,7 @@ # Beryllium atom. xyz_Be = [ - ("Be" , (0., 0., 0.)) + ("Be", (0., 0., 0.)) ] mol_Be_321g = SecondQuantizedMolecule(xyz_Be, q=0, spin=0, basis="3-21g", frozen_orbitals=None) diff --git a/tangelo/problem_decomposition/dmet/__init__.py b/tangelo/problem_decomposition/dmet/__init__.py index 81a799660..532746351 100644 --- a/tangelo/problem_decomposition/dmet/__init__.py +++ b/tangelo/problem_decomposition/dmet/__init__.py @@ -11,4 +11,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - diff --git a/tangelo/problem_decomposition/dmet/_helpers/dmet_bath.py b/tangelo/problem_decomposition/dmet/_helpers/dmet_bath.py index 5311385cc..7c6db3c78 100644 --- a/tangelo/problem_decomposition/dmet/_helpers/dmet_bath.py +++ b/tangelo/problem_decomposition/dmet/_helpers/dmet_bath.py @@ -20,6 +20,7 @@ import numpy as np + def dmet_fragment_bath(mol, t_list, temp_list, onerdm_low): """ Construct the bath orbitals for DMET fragment calculation. @@ -51,6 +52,7 @@ def dmet_fragment_bath(mol, t_list, temp_list, onerdm_low): return bath_orb, e_core + def dmet_onerdm_embed(mol, temp_list, onerdm_before): """ Extract the one particle RDM of the active space. @@ -74,19 +76,20 @@ def dmet_onerdm_embed(mol, temp_list, onerdm_before): if temp_list[0] == 0: # If it is the first fragment, just determine the maximum for extraction - onerdm_temp = onerdm_matrix[ : , temp_list[1]: ] - onerdm_temp3 = onerdm_temp[temp_list[1]: , : ] + onerdm_temp = onerdm_matrix[:, temp_list[1]:] + onerdm_temp3 = onerdm_temp[temp_list[1]:, :] else: # Determine the minimum and maximum orbitals for extraction - onerdm_temp = onerdm_matrix[ : , : temp_list[0]] - onerdm_temp2 = onerdm_matrix[ : , temp_list[1]: ] + onerdm_temp = onerdm_matrix[:, : temp_list[0]] + onerdm_temp2 = onerdm_matrix[:, temp_list[1]:] onerdm_temp3 = np.hstack((onerdm_temp, onerdm_temp2)) - onerdm_temp = onerdm_temp3[ : temp_list[0], : ] - onerdm_temp2 = onerdm_temp3[temp_list[1]: , : ] + onerdm_temp = onerdm_temp3[:temp_list[0], :] + onerdm_temp2 = onerdm_temp3[temp_list[1]:, :] onerdm_temp3 = np.vstack((onerdm_temp, onerdm_temp2)) return onerdm_temp3 + def dmet_bath_orb_sort(t_list, e_before, c_before): """ Sort the bath orbitals with the eigenvalues (orbital energies). @@ -113,11 +116,12 @@ def dmet_bath_orb_sort(t_list, e_before, c_before): # Sort the bath orbitals with its energies e_new = e_before[new_index] - c_new = c_before[ : , new_index] + c_new = c_before[:, new_index] return e_new, c_new -def dmet_add_to_bath_orb( mol, t_list, temp_list, e_before, c_before ): + +def dmet_add_to_bath_orb(mol, t_list, temp_list, e_before, c_before): """ Add the frozen core part to the bath orbitals. Args: @@ -137,12 +141,12 @@ def dmet_add_to_bath_orb( mol, t_list, temp_list, e_before, c_before ): """ # Copy the bath orbitals and energies be fore adding the core - add_e = - e_before[t_list[1]: ] - add_c = c_before[ : , t_list[1]: ] + add_e = - e_before[t_list[1]:] + add_c = c_before[:, t_list[1]:] new_index = add_e.argsort() # Sort the orbitals based on its energies - c_before[ : , t_list[1]: ] = add_c[ : , new_index] + c_before[:, t_list[1]:] = add_c[:, new_index] add_e = - add_e[new_index] # The orbital energies with core part @@ -153,7 +157,7 @@ def dmet_add_to_bath_orb( mol, t_list, temp_list, e_before, c_before ): for orb in range(0, t_list[0]): c_before = np.insert(c_before, orb, 0.0, axis=1) i_temp = 0 - for orb_total in range( 0, norbital_total ): + for orb_total in range(0, norbital_total): if ((orb_total >= temp_list[0]) and (orb_total < temp_list[1])): c_before = np.insert(c_before, orb_total, 0.0, axis=0) c_before[orb_total, i_temp] = 1.0 diff --git a/tangelo/problem_decomposition/dmet/_helpers/dmet_fragment.py b/tangelo/problem_decomposition/dmet/_helpers/dmet_fragment.py index fc9ed6f29..5d2ea55f8 100644 --- a/tangelo/problem_decomposition/dmet/_helpers/dmet_fragment.py +++ b/tangelo/problem_decomposition/dmet/_helpers/dmet_fragment.py @@ -18,6 +18,7 @@ fragment, from the atom list, showing how many atoms included in each fragment. """ + def dmet_fragment_constructor(mol, atom_list, number_fragment): """Construct orbital list. @@ -38,19 +39,19 @@ def dmet_fragment_constructor(mol, atom_list, number_fragment): """ # Make a new atom list based on how many fragments for DMET calculation - if number_fragment == 0 : + if number_fragment == 0: atom_list2 = atom_list else: # Calculate the number of DMET calculations - number_new_fragment = int(len(atom_list)/(number_fragment+1)) # number of DMET calulation per loop + number_new_fragment = int(len(atom_list)/(number_fragment+1)) # number of DMET calulation per loop atom_list2 = [] # Define the number of atoms per DMET calculation - for i in range( number_new_fragment ): + for i in range(number_new_fragment): num = 0 - for j in range( number_fragment + 1 ): + for j in range(number_fragment + 1): k = (number_fragment+1)*i+j - num += atom_list[ k ] + num += atom_list[k] atom_list2.append(num) # Initialize the list of the number of orbitals @@ -69,11 +70,11 @@ def dmet_fragment_constructor(mol, atom_list, number_fragment): item = total_basis.split() item0 = int(item[0]) if (isum <= item0 <= isum2): - itemp+=1 + itemp += 1 isum += i jorb += itemp orb_list.append(itemp) - orb_list2.append([iorb,jorb]) + orb_list2.append([iorb, jorb]) iorb += itemp return orb_list, orb_list2, atom_list2 diff --git a/tangelo/problem_decomposition/dmet/_helpers/dmet_onerdm.py b/tangelo/problem_decomposition/dmet/_helpers/dmet_onerdm.py index 35c4877ca..65b7ba2ee 100644 --- a/tangelo/problem_decomposition/dmet/_helpers/dmet_onerdm.py +++ b/tangelo/problem_decomposition/dmet/_helpers/dmet_onerdm.py @@ -20,6 +20,7 @@ import numpy as np from functools import reduce + def dmet_low_rdm(active_fock, number_active_electrons): """Construct the one-particle RDM from low-level calculation. @@ -37,11 +38,12 @@ def dmet_low_rdm(active_fock, number_active_electrons): e, c = np.linalg.eigh(active_fock) new_index = e.argsort() e = e[new_index] - c = c[ : , new_index] - onerdm = np.dot(c[ : , : int(num_occ)], c[ : , : int(num_occ)].T) * 2 + c = c[:, new_index] + onerdm = np.dot(c[:, : int(num_occ)], c[:, : int(num_occ)].T) * 2 return onerdm + def dmet_fragment_rdm(t_list, bath_orb, core_occupied, number_active_electrons): """Construct the one-particle RDM for the core orbitals. diff --git a/tangelo/problem_decomposition/dmet/_helpers/dmet_orbitals.py b/tangelo/problem_decomposition/dmet/_helpers/dmet_orbitals.py index a63d74d2c..dd73489c7 100644 --- a/tangelo/problem_decomposition/dmet/_helpers/dmet_orbitals.py +++ b/tangelo/problem_decomposition/dmet/_helpers/dmet_orbitals.py @@ -26,6 +26,7 @@ import numpy as np from functools import reduce + class dmet_orbitals: """ Localize the SCF orbitals and calculate the integrals @@ -81,7 +82,7 @@ def __init__(self, mol, mf, active_space, localization_function): self.dmet_active_orbitals = np.zeros([mf.mol.nao_nr()], dtype=int) self.dmet_active_orbitals[active_space] = 1 self.number_active_orbitals = np.sum(self.dmet_active_orbitals) - self.number_active_electrons = int(np.rint(mf.mol.nelectron - np.sum(mf.mo_occ[self.dmet_active_orbitals==0]))) + self.number_active_electrons = int(np.rint(mf.mol.nelectron - np.sum(mf.mo_occ[self.dmet_active_orbitals == 0]))) # Localize the orbitals (IAO) self.localized_mo = localization_function(mol, mf) @@ -116,17 +117,17 @@ def dmet_fragment_hamiltonian(self, bath_orb, norb_high, onerdm_core): """ # Calculate one-electron integrals - frag_oneint = reduce(np.dot, (bath_orb[ : , : norb_high].T, self.active_oneint, bath_orb[ : , : norb_high])) + frag_oneint = reduce(np.dot, (bath_orb[:, : norb_high].T, self.active_oneint, bath_orb[:, : norb_high])) # Calculate the fock matrix density_matrix = reduce(np.dot, (self.localized_mo, onerdm_core, self.localized_mo.T)) two_int = scf.hf.get_veff(self.mol_full, density_matrix, 0, 0, 1) new_fock = self.active_oneint + reduce(np.dot, ((self.localized_mo.T, two_int, self.localized_mo))) - frag_fock = reduce(np.dot, (bath_orb[ : , : norb_high ].T, new_fock, bath_orb[ : , : norb_high])) + frag_fock = reduce(np.dot, (bath_orb[:, : norb_high].T, new_fock, bath_orb[:, : norb_high])) # Calculate the two-electron integrals - coefficients = np.dot(self.localized_mo, bath_orb[ : , : norb_high]) - frag_twoint = ao2mo.outcore.full_iofree(self.mol_full, coefficients, compact=False).reshape( \ + coefficients = np.dot(self.localized_mo, bath_orb[:, : norb_high]) + frag_twoint = ao2mo.outcore.full_iofree(self.mol_full, coefficients, compact=False).reshape( norb_high, norb_high, norb_high, norb_high) return frag_oneint, frag_fock, frag_twoint diff --git a/tangelo/problem_decomposition/dmet/_helpers/dmet_scf.py b/tangelo/problem_decomposition/dmet/_helpers/dmet_scf.py index db5194158..472cb1afe 100644 --- a/tangelo/problem_decomposition/dmet/_helpers/dmet_scf.py +++ b/tangelo/problem_decomposition/dmet/_helpers/dmet_scf.py @@ -21,6 +21,7 @@ from functools import reduce import numpy as np + def dmet_fragment_scf(t_list, two_ele, fock, number_electrons, number_orbitals, guess_orbitals, chemical_potential): """Perform SCF calculation. @@ -68,12 +69,12 @@ def dmet_fragment_scf(t_list, two_ele, fock, number_electrons, number_orbitals, dm_frag = reduce(np.dot, (mf_frag.mo_coeff, np.diag(mf_frag.mo_occ), mf_frag.mo_coeff.T)) # Use newton-raphson algorithm if the above SCF calculation is not converged - if (mf_frag.converged == False): + if (mf_frag.converged is False): mf_frag.get_hcore = lambda *args: fock_frag_copy mf_frag.get_ovlp = lambda *args: np.eye(number_orbitals) mf_frag._eri = ao2mo.restore(8, two_ele, number_orbitals) mf_frag = scf.RHF(mol_frag).newton() - energy = mf_frag.kernel(dm0 = dm_frag) + energy = mf_frag.kernel(dm0=dm_frag) dm_frag = reduce(np.dot, (mf_frag.mo_coeff, np.diag(mf_frag.mo_occ), mf_frag.mo_coeff.T)) return mf_frag, fock_frag_copy, mol_frag diff --git a/tangelo/problem_decomposition/dmet/_helpers/dmet_scf_guess.py b/tangelo/problem_decomposition/dmet/_helpers/dmet_scf_guess.py index 2bd3c0d92..a60503006 100644 --- a/tangelo/problem_decomposition/dmet/_helpers/dmet_scf_guess.py +++ b/tangelo/problem_decomposition/dmet/_helpers/dmet_scf_guess.py @@ -22,6 +22,7 @@ import numpy as np from functools import reduce + def dmet_fragment_guess(t_list, bath_orb, chemical_potential, norb_high, number_active_electron, active_fock): """Construct the guess orbitals. @@ -40,7 +41,7 @@ def dmet_fragment_guess(t_list, bath_orb, chemical_potential, norb_high, number_ """ # Construct the fock matrix of the fragment (subtract the chemical potential for consistency) - fock_fragment = reduce(np.dot, (bath_orb[ : , : norb_high].T, active_fock, bath_orb[ : , : norb_high])) + fock_fragment = reduce(np.dot, (bath_orb[:, : norb_high].T, active_fock, bath_orb[:, : norb_high])) norb = t_list[0] if(chemical_potential != 0): for i in range(norb): @@ -48,7 +49,7 @@ def dmet_fragment_guess(t_list, bath_orb, chemical_potential, norb_high, number_ # Diagonalize the fock matrix and get the eigenvectors eigenvalues, eigenvectors = scipy.linalg.eigh(fock_fragment) - eigenvectors = eigenvectors[ : , eigenvalues.argsort()] + eigenvectors = eigenvectors[:, eigenvalues.argsort()] # Extract the eigenvectors of the occupied orbitals as the guess orbitals frag_guess = np.dot(eigenvectors[ :, : int(number_active_electron/2)], eigenvectors[ :, : int(number_active_electron/2)].T) * 2 diff --git a/tangelo/problem_decomposition/dmet/dmet_problem_decomposition.py b/tangelo/problem_decomposition/dmet/dmet_problem_decomposition.py index ab4bc7ad9..ffd07d93e 100644 --- a/tangelo/problem_decomposition/dmet/dmet_problem_decomposition.py +++ b/tangelo/problem_decomposition/dmet/dmet_problem_decomposition.py @@ -285,7 +285,8 @@ def energy_error_bars(self, n_shots, n_resamples, purify=False, rdm_measurements # begin resampling resampled_energies = np.zeros(n_resamples, dtype=float) for i in range(n_resamples): - _ = self._oneshot_loop(self.chemical_potential, save_results=False, resample=True, n_shots=n_shots, purify=purify, rdm_measurements=rdm_measurements) + _ = self._oneshot_loop(self.chemical_potential, save_results=False, resample=True, + n_shots=n_shots, purify=purify, rdm_measurements=rdm_measurements) resampled_energies[i] = self.dmet_energy.real energy_average, energy_standard_deviation = np.mean(resampled_energies), np.std(resampled_energies, ddof=1) diff --git a/tangelo/problem_decomposition/dmet/fragment.py b/tangelo/problem_decomposition/dmet/fragment.py index 3c5e7dfbc..5d50535b6 100644 --- a/tangelo/problem_decomposition/dmet/fragment.py +++ b/tangelo/problem_decomposition/dmet/fragment.py @@ -72,7 +72,7 @@ def _get_fermionic_hamiltonian(self): FermionOperator: Self-explanatory. """ - dummy_of_molecule = openfermion.MolecularData([["C", (0., 0. ,0.)]], "sto-3g", self.spin+1, self.q) + dummy_of_molecule = openfermion.MolecularData([["C", (0., 0., 0.)]], "sto-3g", self.spin+1, self.q) # Overwrting nuclear repulsion term. dummy_of_molecule.nuclear_repulsion = self.mean_field.mol.energy_nuc() diff --git a/tangelo/problem_decomposition/electron_localization/iao_localization.py b/tangelo/problem_decomposition/electron_localization/iao_localization.py index 99cd3dc24..609828fc0 100644 --- a/tangelo/problem_decomposition/electron_localization/iao_localization.py +++ b/tangelo/problem_decomposition/electron_localization/iao_localization.py @@ -57,6 +57,7 @@ def iao_localization(mol, mf): return iao_lo + def _iao_occupied_orbitals(mol, mf): """Get the IAOs for occupied space. @@ -106,6 +107,7 @@ def _iao_occupied_orbitals(mol, mf): return iao_active + def _iao_complementary_orbitals(mol, iao_ref): """Get the IAOs for complementary space (virtual orbitals). @@ -133,8 +135,8 @@ def _iao_complementary_orbitals(mol, iao_ref): norbital_active, active_list = _iao_count_active(mol, min_mol) # Obtain the Overlap-like matrices - s21 = s1[active_list, : ] - s2 = s21[ : , active_list] + s21 = s1[active_list, :] + s2 = s21[:, active_list] s12 = s21.T # Calculate P_12 = S_1^-1 * S_12 using Cholesky decomposition @@ -157,6 +159,7 @@ def _iao_complementary_orbitals(mol, iao_ref): return iao_comp + def _iao_count_active(mol, min_mol): """Figure out the basis functions matching with MINAO. @@ -183,6 +186,7 @@ def _iao_count_active(mol, min_mol): return number_active, active_number_list + def _iao_complementary_space(iao_ref, s, number_inactive): """Determine the complementary space orbitals. @@ -207,6 +211,7 @@ def _iao_complementary_space(iao_ref, s, number_inactive): return eigen_vectors + def _iao_atoms(mol, iao1, iao2): """Assign IAO to atom centers and rearrange the IAOs. @@ -226,9 +231,9 @@ def _iao_atoms(mol, iao1, iao2): iao_combine = np.hstack((iao1, iao2)) # Calculate atom center for each orbital - x = np.diag(reduce(np.dot,(iao_combine.T, r_int1e[0], iao_combine))) - y = np.diag(reduce(np.dot,(iao_combine.T, r_int1e[1], iao_combine))) - z = np.diag(reduce(np.dot,(iao_combine.T, r_int1e[2], iao_combine))) + x = np.diag(reduce(np.dot, (iao_combine.T, r_int1e[0], iao_combine))) + y = np.diag(reduce(np.dot, (iao_combine.T, r_int1e[1], iao_combine))) + z = np.diag(reduce(np.dot, (iao_combine.T, r_int1e[2], iao_combine))) # Align the coordinates orbitals_temp = np.vstack((x, y, z)) @@ -241,7 +246,7 @@ def _iao_atoms(mol, iao1, iao2): orb_list = _dmet_orb_list(mol, atom_list) # Rearrange the orbitals - iao_combine = iao_combine[ : , orb_list] + iao_combine = iao_combine[:, orb_list] # Orthogonalize the orbitals s1 = mol.intor_symmetric("int1e_ovlp") @@ -249,6 +254,7 @@ def _iao_atoms(mol, iao1, iao2): return iao_combine + def _dmet_atom_list(mol, orbitals): """Assign IAO to atom centers and rearrange the IAOs. diff --git a/tangelo/problem_decomposition/electron_localization/meta_lowdin_localization.py b/tangelo/problem_decomposition/electron_localization/meta_lowdin_localization.py index 0c0a30ad0..6b0dd9597 100644 --- a/tangelo/problem_decomposition/electron_localization/meta_lowdin_localization.py +++ b/tangelo/problem_decomposition/electron_localization/meta_lowdin_localization.py @@ -23,6 +23,7 @@ from pyscf.lo import orth + def meta_lowdin_localization(mol, mf): """Localize the orbitals using Meta-Löwdin localization. diff --git a/tangelo/problem_decomposition/oniom/__init__.py b/tangelo/problem_decomposition/oniom/__init__.py index 81a799660..532746351 100644 --- a/tangelo/problem_decomposition/oniom/__init__.py +++ b/tangelo/problem_decomposition/oniom/__init__.py @@ -11,4 +11,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - diff --git a/tangelo/problem_decomposition/oniom/_helpers/__init__.py b/tangelo/problem_decomposition/oniom/_helpers/__init__.py index 81a799660..532746351 100644 --- a/tangelo/problem_decomposition/oniom/_helpers/__init__.py +++ b/tangelo/problem_decomposition/oniom/_helpers/__init__.py @@ -11,4 +11,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - diff --git a/tangelo/problem_decomposition/oniom/_helpers/helper_classes.py b/tangelo/problem_decomposition/oniom/_helpers/helper_classes.py index 5f8135bcf..516720967 100644 --- a/tangelo/problem_decomposition/oniom/_helpers/helper_classes.py +++ b/tangelo/problem_decomposition/oniom/_helpers/helper_classes.py @@ -90,7 +90,7 @@ def build(self): # Basis is only relevant when computing the mean-field. After this, # it is discarded (not needed for electronic solver because they # retrieved it from the molecule object). - self.options_low = {i:self.options_low[i] for i in self.options_low if i not in ["basis", "frozen_orbitals"]} + self.options_low = {i: self.options_low[i] for i in self.options_low if i not in ["basis", "frozen_orbitals"]} self.solver_low = self.get_solver(self.mol_low, self.solver_low, self.options_low) @@ -101,7 +101,7 @@ def build(self): if self.mol_high is None: self.mol_high = self.get_mol(self.options_high["basis"], self.options_high.get("frozen_orbitals", None)) # Same process done as in low accuracy process. - self.options_high = {i:self.options_high[i] for i in self.options_high if i not in ["basis", "frozen_orbitals"]} + self.options_high = {i: self.options_high[i] for i in self.options_high if i not in ["basis", "frozen_orbitals"]} self.solver_high = self.get_solver(self.mol_high, self.solver_high, self.options_high) diff --git a/tangelo/problem_decomposition/oniom/oniom_problem_decomposition.py b/tangelo/problem_decomposition/oniom/oniom_problem_decomposition.py index 8999b9709..a8f9ae292 100644 --- a/tangelo/problem_decomposition/oniom/oniom_problem_decomposition.py +++ b/tangelo/problem_decomposition/oniom/oniom_problem_decomposition.py @@ -100,7 +100,7 @@ def distribute_atoms(self): fragment.geometry += [li.relink(self.geometry) for li in fragment.broken_links] def simulate(self): - """Run the ONIOM core-method. The total energy is defined as + r"""Run the ONIOM core-method. The total energy is defined as E_ONIOM = E_LOW[SYSTEM] + \sum_i {E_HIGH_i[MODEL_i] - E_LOW_i[MODEL_i]} Returns: diff --git a/tangelo/problem_decomposition/tests/__init__.py b/tangelo/problem_decomposition/tests/__init__.py index 81a799660..532746351 100644 --- a/tangelo/problem_decomposition/tests/__init__.py +++ b/tangelo/problem_decomposition/tests/__init__.py @@ -11,4 +11,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - diff --git a/tangelo/problem_decomposition/tests/dmet/__init__.py b/tangelo/problem_decomposition/tests/dmet/__init__.py index 81a799660..532746351 100644 --- a/tangelo/problem_decomposition/tests/dmet/__init__.py +++ b/tangelo/problem_decomposition/tests/dmet/__init__.py @@ -11,4 +11,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - diff --git a/tangelo/problem_decomposition/tests/dmet/test_dmet.py b/tangelo/problem_decomposition/tests/dmet/test_dmet.py index e6d753667..24f23152f 100644 --- a/tangelo/problem_decomposition/tests/dmet/test_dmet.py +++ b/tangelo/problem_decomposition/tests/dmet/test_dmet.py @@ -166,7 +166,7 @@ def test_fragment_ids_exceptions(self): """ opt_dmet = {"molecule": mol_H4_doublecation_321g, - "fragment_atoms": [[0,0], [1], [2], [3]], + "fragment_atoms": [[0, 0], [1], [2], [3]], "fragment_solvers": "ccsd", "electron_localization": Localization.iao, "verbose": False diff --git a/tangelo/problem_decomposition/tests/dmet/test_dmet_oneshot_loop.py b/tangelo/problem_decomposition/tests/dmet/test_dmet_oneshot_loop.py index 8a26b06ab..6eb5f146e 100644 --- a/tangelo/problem_decomposition/tests/dmet/test_dmet_oneshot_loop.py +++ b/tangelo/problem_decomposition/tests/dmet/test_dmet_oneshot_loop.py @@ -107,8 +107,8 @@ def test_dmet_functions(self): mf_fragments, fock_frag_copy, mol = dmet_fragment_scf(t_list, two_ele, fock, nelec_high, norb_high, guess_orbitals, chemical_potential) # Test the energy calculation and construction of the one-particle RDM from the CC calculation for a fragment - #fragment_energy, onerdm_frag, _, _ = dmet_fragment_cc_classical(mf_fragments, fock_frag_copy, t_list, one_ele, two_ele, fock) - #self.assertAlmostEqual(fragment_energy, -82.70210049368914, msg="The DMET energy does no agree", delta=1e-6) + # fragment_energy, onerdm_frag, _, _ = dmet_fragment_cc_classical(mf_fragments, fock_frag_copy, t_list, one_ele, two_ele, fock) + # self.assertAlmostEqual(fragment_energy, -82.70210049368914, msg="The DMET energy does no agree", delta=1e-6) if __name__ == "__main__": diff --git a/tangelo/problem_decomposition/tests/dmet/test_dmet_orbitals.py b/tangelo/problem_decomposition/tests/dmet/test_dmet_orbitals.py index 4721b68f8..e693dc85f 100644 --- a/tangelo/problem_decomposition/tests/dmet/test_dmet_orbitals.py +++ b/tangelo/problem_decomposition/tests/dmet/test_dmet_orbitals.py @@ -24,6 +24,7 @@ path_file = os.path.dirname(os.path.abspath(__file__)) + class TestDMETorbitals(unittest.TestCase): """Generate the localized orbitals employing IAOs.""" diff --git a/tangelo/problem_decomposition/tests/oniom/__init__.py b/tangelo/problem_decomposition/tests/oniom/__init__.py index 81a799660..532746351 100644 --- a/tangelo/problem_decomposition/tests/oniom/__init__.py +++ b/tangelo/problem_decomposition/tests/oniom/__init__.py @@ -11,4 +11,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - diff --git a/tangelo/toolboxes/__init__.py b/tangelo/toolboxes/__init__.py index 81a799660..532746351 100644 --- a/tangelo/toolboxes/__init__.py +++ b/tangelo/toolboxes/__init__.py @@ -11,4 +11,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - diff --git a/tangelo/toolboxes/ansatz_generator/__init__.py b/tangelo/toolboxes/ansatz_generator/__init__.py index 81a799660..532746351 100644 --- a/tangelo/toolboxes/ansatz_generator/__init__.py +++ b/tangelo/toolboxes/ansatz_generator/__init__.py @@ -11,4 +11,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - diff --git a/tangelo/toolboxes/ansatz_generator/_general_unitary_cc.py b/tangelo/toolboxes/ansatz_generator/_general_unitary_cc.py index 67534a2ec..3c379718f 100644 --- a/tangelo/toolboxes/ansatz_generator/_general_unitary_cc.py +++ b/tangelo/toolboxes/ansatz_generator/_general_unitary_cc.py @@ -55,7 +55,7 @@ def hermitian_conjugate(terms): (v[0][1][0], 0), (v[0][0][0], 0)), -v[1]] for v in terms] - except: + except Exception: raise ValueError("Input terms must be format as, e.g. [[((int,1),(int,0),(int,1),(int,0)),float],...]") diff --git a/tangelo/toolboxes/ansatz_generator/_qubit_cc.py b/tangelo/toolboxes/ansatz_generator/_qubit_cc.py index aeeee9319..e270cee36 100644 --- a/tangelo/toolboxes/ansatz_generator/_qubit_cc.py +++ b/tangelo/toolboxes/ansatz_generator/_qubit_cc.py @@ -67,11 +67,11 @@ def construct_dis(pure_var_params, qubit_ham, qcc_deriv_thresh, verbose=False): dis_group_gens = get_gens_from_idxs(dis_group_idxs) dis.append(dis_group_gens) if verbose: - print(f"DIS group {i} | group size = {len(dis_group_gens)} | "\ - f"flip indices = {dis_group_idxs} | |dEQCC/dtau| = "\ + print(f"DIS group {i} | group size = {len(dis_group_gens)} | " + f"flip indices = {dis_group_idxs} | |dEQCC/dtau| = " f"{abs(dis_group[1])} a.u.\n") else: - raise ValueError(f"The DIS is empty: there are no candidate DIS groups where "\ + raise ValueError(f"The DIS is empty: there are no candidate DIS groups where " f"|dEQCC/dtau| >= {qcc_deriv_thresh} a.u. Terminate the QCC simulation.\n") return dis @@ -90,7 +90,7 @@ def get_dis_groups(pure_var_params, qubit_ham, qcc_deriv_thresh): """ # Get the flip indices from qubit_ham and compute the gradient dEQCC/dtau - qham_gen = ((qham_items[0], (qham_items[1], pure_var_params))\ + qham_gen = ((qham_items[0], (qham_items[1], pure_var_params)) for qham_items in qubit_ham.terms.items()) flip_idxs = list(filter(None, (get_idxs_deriv(q_gen[0], *q_gen[1]) for q_gen in qham_gen))) @@ -101,7 +101,7 @@ def get_dis_groups(pure_var_params, qubit_ham, qcc_deriv_thresh): candidates[idxs[0]] = idxs[1] + deriv_old # Return a sorted list of flip indices and signed dEQCC/dtau values for each DIS group - dis_groups = [idxs_deriv for idxs_deriv in candidates.items()\ + dis_groups = [idxs_deriv for idxs_deriv in candidates.items() if abs(idxs_deriv[1]) >= qcc_deriv_thresh] return sorted(dis_groups, key=lambda deriv: abs(deriv[1]), reverse=True) diff --git a/tangelo/toolboxes/ansatz_generator/_qubit_mf.py b/tangelo/toolboxes/ansatz_generator/_qubit_mf.py index 79ae9923e..5a157e877 100644 --- a/tangelo/toolboxes/ansatz_generator/_qubit_mf.py +++ b/tangelo/toolboxes/ansatz_generator/_qubit_mf.py @@ -51,7 +51,7 @@ def get_op_expval(qubit_op, qmf_var_params): """ n_qubits = qmf_var_params.size // 2 - qubit_op_gen = ((qop_items[0], (qop_items[1], qmf_var_params, n_qubits))\ + qubit_op_gen = ((qop_items[0], (qop_items[1], qmf_var_params, n_qubits)) for qop_items in qubit_op.terms.items()) return sum([calc_op_expval(qop_gen[0], *qop_gen[1]) for qop_gen in qubit_op_gen]) @@ -105,7 +105,7 @@ def init_qmf_from_hf(n_spinorbitals, n_electrons, mapping, up_then_down=False, s return np.concatenate((np.pi * thetas, np.zeros((len(thetas),), dtype=float))) -def purify_qmf_state(qmf_var_params, n_spinorbitals, n_electrons, mapping, up_then_down=False,\ +def purify_qmf_state(qmf_var_params, n_spinorbitals, n_electrons, mapping, up_then_down=False, spin=None, verbose=False): """The efficient construction and screening of the DIS requires a z-collinear QMF state. If the QMF state specified by qmf_var_params is not z-collinear, this function adjusts the diff --git a/tangelo/toolboxes/ansatz_generator/adapt_ansatz.py b/tangelo/toolboxes/ansatz_generator/adapt_ansatz.py index 9722148cd..c4531a5d6 100644 --- a/tangelo/toolboxes/ansatz_generator/adapt_ansatz.py +++ b/tangelo/toolboxes/ansatz_generator/adapt_ansatz.py @@ -44,7 +44,7 @@ class ADAPTAnsatz(Ansatz): """ def __init__(self, n_spinorbitals, n_electrons, ansatz_options=None): - default_options = {"operators": list(), "ferm_operators":list(), + default_options = {"operators": list(), "ferm_operators": list(), "mapping": "jw", "up_then_down": False, "reference_state": "HF"} diff --git a/tangelo/toolboxes/ansatz_generator/fermionic_operators.py b/tangelo/toolboxes/ansatz_generator/fermionic_operators.py index 298787916..08104d06d 100644 --- a/tangelo/toolboxes/ansatz_generator/fermionic_operators.py +++ b/tangelo/toolboxes/ansatz_generator/fermionic_operators.py @@ -27,7 +27,7 @@ def number_operator(n_orbs, up_then_down=False): r"""Function to generate the normal ordered number operator as a - FermionicOperator. + FermionOperator. Args: n_orbs (int): number of orbitals in the fermion basis (this is number of @@ -36,7 +36,7 @@ def number_operator(n_orbs, up_then_down=False): openfermion (False). Returns: - FermionicOperator: The number operator penalty \hat{N}. + FermionOperator: The number operator penalty \hat{N}. """ all_terms = number_operator_list(n_orbs, up_then_down) @@ -75,7 +75,7 @@ def spinz_operator(n_orbs, up_then_down=False): openfermion (False). Returns: - FermionicOperator: The Sz operator \hat{Sz}. + FermionOperator: The Sz operator \hat{Sz}. """ all_terms = spinz_operator_list(n_orbs, up_then_down) @@ -106,7 +106,7 @@ def spinz_operator_list(n_orbs, up_then_down=False): def spin2_operator(n_orbs, up_then_down=False): r"""Function to generate the normal ordered S^2 operator as a - FermionicOperator. + FermionOperator. Args: n_orbs (int): number of orbitals in the fermion basis (this is number of @@ -115,7 +115,7 @@ def spin2_operator(n_orbs, up_then_down=False): openfermion (False). Returns: - FermionicOperator: The S^2 operator \hat{S}^2. + FermionOperator: The S^2 operator \hat{S}^2. """ all_terms = spin2_operator_list(n_orbs, up_then_down) @@ -125,8 +125,7 @@ def spin2_operator(n_orbs, up_then_down=False): def spin2_operator_list(n_orbs, up_then_down=False): - r"""Function to generate the normal ordered S^2 operator as a - FermionicOperator. + r"""Function to generate the normal ordered S^2 operator as a list. Args: n_orbs (int): number of orbitals in the fermion basis (this is number of diff --git a/tangelo/toolboxes/ansatz_generator/hea.py b/tangelo/toolboxes/ansatz_generator/hea.py index 0915b216c..c56ceaab3 100644 --- a/tangelo/toolboxes/ansatz_generator/hea.py +++ b/tangelo/toolboxes/ansatz_generator/hea.py @@ -113,8 +113,8 @@ def set_var_params(self, var_params=None): else: initial_var_params = np.array(var_params) if initial_var_params.size != self.n_var_params: - raise ValueError(f"Expected {self.n_var_params} variational parameters but "\ - f"received {initial_var_params.size}.") + raise ValueError(f"Expected {self.n_var_params} variational parameters but " + f"received {initial_var_params.size}.") self.var_params = initial_var_params return initial_var_params diff --git a/tangelo/toolboxes/ansatz_generator/penalty_terms.py b/tangelo/toolboxes/ansatz_generator/penalty_terms.py index 7996e7a9f..262be7e93 100644 --- a/tangelo/toolboxes/ansatz_generator/penalty_terms.py +++ b/tangelo/toolboxes/ansatz_generator/penalty_terms.py @@ -29,7 +29,7 @@ def number_operator_penalty(n_orbs, n_electrons, mu=1, up_then_down=False): r"""Function to generate the normal ordered number operator penalty term as - a FermionicOperator. + a FermionOperator. Args: n_orbs (int): number of orbitals in the fermion basis (this is number of @@ -42,7 +42,7 @@ def number_operator_penalty(n_orbs, n_electrons, mu=1, up_then_down=False): mapping handle the ordering. Returns: - FermionicOperator: The number operator penalty term + FermionOperator: The number operator penalty term mu*(\hat{N}-n_electrons)^2. """ @@ -65,7 +65,7 @@ def spin_operator_penalty(n_orbs, sz, mu=1, up_then_down=False): mapping handle the ordering. Returns: - FermionicOperator: The Sz operator penalty term mu*(\hat{Sz}-sz)^2. + FermionOperator: The Sz operator penalty term mu*(\hat{Sz}-sz)^2. """ all_terms = [[(), -sz]] + spinz_operator_list(n_orbs, up_then_down) @@ -91,7 +91,7 @@ def spin2_operator_penalty(n_orbs, s2, mu=1, up_then_down=False): mapping handle the ordering. Returns: - FermionicOperator: The S^2 operator penalty term mu*(\hat{S}^2-s2)^2. + FermionOperator: The S^2 operator penalty term mu*(\hat{S}^2-s2)^2. """ all_terms = [[(), -s2]] + spin2_operator_list(n_orbs, up_then_down) @@ -123,7 +123,7 @@ def combined_penalty(n_orbs, opt_penalty_terms=None, up_then_down=False): mapping handle this. Returns: - FermionicOperator: The combined n_electron+sz+s^2 penalty + FermionOperator: The combined n_electron+sz+s^2 penalty terms. """ diff --git a/tangelo/toolboxes/ansatz_generator/qcc.py b/tangelo/toolboxes/ansatz_generator/qcc.py index 47b1323f6..c6be5852e 100755 --- a/tangelo/toolboxes/ansatz_generator/qcc.py +++ b/tangelo/toolboxes/ansatz_generator/qcc.py @@ -77,8 +77,8 @@ class QCC(Ansatz): verbose (bool): Flag for QCC verbosity. Default, False. """ - def __init__(self, molecule, mapping="JW", up_then_down=False, qubit_op_list=None,\ - qmf_circuit=None, qmf_var_params=None, qubit_mf_ham=None, qcc_guess=1.e-1,\ + def __init__(self, molecule, mapping="JW", up_then_down=False, qubit_op_list=None, + qmf_circuit=None, qmf_var_params=None, qubit_mf_ham=None, qcc_guess=1.e-1, qcc_deriv_thresh=1.e-3, max_qcc_gens=None, verbose=False): self.molecule = molecule @@ -92,7 +92,7 @@ def __init__(self, molecule, mapping="JW", up_then_down=False, qubit_op_list=Non self.n_qubits = get_qubit_number(self.mapping, self.n_spinorbitals) self.up_then_down = up_then_down if self.mapping.upper() == "JW" and not self.up_then_down: - warnings.warn("The QCC ansatz requires spin-orbital ordering to be all spin-up "\ + warnings.warn("The QCC ansatz requires spin-orbital ordering to be all spin-up " "first followed by all spin-down for the JW mapping.", RuntimeWarning) self.up_then_down = True @@ -106,14 +106,14 @@ def __init__(self, molecule, mapping="JW", up_then_down=False, qubit_op_list=Non if qubit_mf_ham is None: self.fermi_ham = self.molecule.fermionic_hamiltonian - self.qubit_ham = fermion_to_qubit_mapping(self.fermi_ham, self.mapping,\ - self.n_spinorbitals, self.n_electrons,\ + self.qubit_ham = fermion_to_qubit_mapping(self.fermi_ham, self.mapping, + self.n_spinorbitals, self.n_electrons, self.up_then_down, self.spin) else: self.qubit_ham = qubit_mf_ham if self.qmf_var_params is None: - self.qmf_var_params = init_qmf_from_hf(self.n_spinorbitals, self.n_electrons,\ + self.qmf_var_params = init_qmf_from_hf(self.n_spinorbitals, self.n_electrons, self.mapping, self.up_then_down, self.spin) elif isinstance(self.qmf_var_params, list): self.qmf_var_params = np.array(self.qmf_var_params) @@ -122,10 +122,10 @@ def __init__(self, molecule, mapping="JW", up_then_down=False, qubit_op_list=Non # Get purified QMF parameters and use them to build the DIS or use a list of generators. if self.qubit_op_list is None: - pure_var_params = purify_qmf_state(self.qmf_var_params, self.n_spinorbitals,\ - self.n_electrons, self.mapping, self.up_then_down,\ + pure_var_params = purify_qmf_state(self.qmf_var_params, self.n_spinorbitals, + self.n_electrons, self.mapping, self.up_then_down, self.spin, self.verbose) - self.dis = construct_dis(pure_var_params, self.qubit_ham, self.qcc_deriv_thresh,\ + self.dis = construct_dis(pure_var_params, self.qubit_ham, self.qcc_deriv_thresh, self.verbose) self.n_var_params = len(self.dis) if self.max_qcc_gens is None\ else min(len(self.dis), self.max_qcc_gens) @@ -159,7 +159,7 @@ def set_var_params(self, var_params=None): if isinstance(var_params, str): var_params = var_params.lower() if var_params not in self.supported_initial_var_params: - raise ValueError(f"Supported keywords for initializing variational parameters: "\ + raise ValueError(f"Supported keywords for initializing variational parameters: " f"{self.supported_initial_var_params}") # Initialize the QCC wave function as |QCC> = |QMF> if var_params == "zeros": @@ -170,7 +170,7 @@ def set_var_params(self, var_params=None): else: initial_var_params = np.array(var_params) if initial_var_params.size != self.n_var_params: - raise ValueError(f"Expected {self.n_var_params} variational parameters but "\ + raise ValueError(f"Expected {self.n_var_params} variational parameters but " f"received {initial_var_params.size}.") self.var_params = initial_var_params return initial_var_params @@ -181,7 +181,7 @@ def prepare_reference_state(self): with the transform used to obtain the qubit operator. """ if self.default_reference_state not in self.supported_reference_state: - raise ValueError(f"Only supported reference state methods are: "\ + raise ValueError(f"Only supported reference state methods are: " f"{self.supported_reference_state}.") if self.default_reference_state == "HF": reference_state_circuit = get_qmf_circuit(self.qmf_var_params, False) @@ -263,10 +263,10 @@ def _get_qcc_qubit_op(self): # Rebuild the DIS in case qubit_ham changed or both the DIS and qubit_op_list don't exist if self.rebuild_dis or (self.dis is None and self.qubit_op_list is None): - pure_var_params = purify_qmf_state(self.qmf_var_params, self.n_spinorbitals,\ - self.n_electrons, self.mapping, self.up_then_down,\ + pure_var_params = purify_qmf_state(self.qmf_var_params, self.n_spinorbitals, + self.n_electrons, self.mapping, self.up_then_down, self.spin, self.verbose) - self.dis = construct_dis(pure_var_params, self.qubit_ham, self.qcc_deriv_thresh,\ + self.dis = construct_dis(pure_var_params, self.qubit_ham, self.qcc_deriv_thresh, self.verbose) self.n_var_params = len(self.dis) if self.max_qcc_gens is None\ else min(len(self.dis), self.max_qcc_gens) @@ -286,6 +286,6 @@ def _get_qcc_qubit_op(self): for i, qcc_gen in enumerate(self.qubit_op_list): qcc_qubit_op -= 0.5 * self.var_params[i] * qcc_gen else: - raise ValueError(f"Expected {self.n_var_params} generators in "\ + raise ValueError(f"Expected {self.n_var_params} generators in " f"{self.qubit_op_list} but received {len(self.qubit_op_list)}.\n") return qcc_qubit_op diff --git a/tangelo/toolboxes/ansatz_generator/qmf.py b/tangelo/toolboxes/ansatz_generator/qmf.py index 1f6a606a1..fc1d2583c 100755 --- a/tangelo/toolboxes/ansatz_generator/qmf.py +++ b/tangelo/toolboxes/ansatz_generator/qmf.py @@ -95,7 +95,7 @@ def __init__(self, molecule, mapping="JW", up_then_down=False, init_qmf=None): self.fermi_ham = self.molecule.fermionic_hamiltonian if isinstance(self.init_qmf, dict): if "init_params" not in self.init_qmf.keys(): - raise KeyError(f"Missing key 'init_params' in {self.init_qmf}. "\ + raise KeyError(f"Missing key 'init_params' in {self.init_qmf}. " f"Supported values are {self.supported_initial_var_params}.") if self.init_qmf["init_params"] in self.supported_initial_var_params: # Set the default QMF parameter procedure @@ -104,7 +104,7 @@ def __init__(self, molecule, mapping="JW", up_then_down=False, init_qmf=None): if self.init_qmf: # Set default penalty term values spin_z = self.spin // 2 - init_qmf_defaults = {"N": (1.5, self.n_electrons),\ + init_qmf_defaults = {"N": (1.5, self.n_electrons), "S^2": (1.5, spin_z * (spin_z + 1)), "Sz": (1.5, spin_z)} # Check if the user requested default values for any penalty terms for term, params in init_qmf_defaults.items(): @@ -114,16 +114,16 @@ def __init__(self, molecule, mapping="JW", up_then_down=False, init_qmf=None): self.fermi_ham += penalize_mf_ham(self.init_qmf, self.n_orbitals) else: if self.var_params_default != "hf_state": - warnings.warn("It is recommended that the QMF parameters are intialized "\ - "using a Hartree-Fock reference state if penalty terms are "\ + warnings.warn("It is recommended that the QMF parameters are intialized " + "using a Hartree-Fock reference state if penalty terms are " "not added to the mean-field Hamiltonian.", RuntimeWarning) else: - raise ValueError(f"Unrecognized value for 'init_params' key in {self.init_qmf} "\ + raise ValueError(f"Unrecognized value for 'init_params' key in {self.init_qmf} " f"Supported values are {self.supported_initial_var_params}.") else: raise TypeError(f"{self.init_qmf} must be dictionary type.") - self.qubit_ham = fermion_to_qubit_mapping(self.fermi_ham, self.mapping, self.n_spinorbitals,\ + self.qubit_ham = fermion_to_qubit_mapping(self.fermi_ham, self.mapping, self.n_spinorbitals, self.n_electrons, self.up_then_down, self.spin) # Default starting parameters for initialization @@ -144,7 +144,7 @@ def set_var_params(self, var_params=None): if isinstance(var_params, str): var_params = var_params.lower() if var_params not in self.supported_initial_var_params: - raise ValueError(f"Supported keywords for initializing variational parameters: "\ + raise ValueError(f"Supported keywords for initializing variational parameters: " f"{self.supported_initial_var_params}") # Initialize |QMF> as |00...0> if var_params == "zeros": @@ -162,12 +162,12 @@ def set_var_params(self, var_params=None): initial_var_params = np.concatenate((initial_thetas, initial_phis)) # Initialize theta angles so that |QMF> = |HF> state and set all phi angles to 0. elif var_params == "hf_state": - initial_var_params = init_qmf_from_hf(self.n_spinorbitals, self.n_electrons,\ + initial_var_params = init_qmf_from_hf(self.n_spinorbitals, self.n_electrons, self.mapping, self.up_then_down, self.spin) else: initial_var_params = np.array(var_params) if initial_var_params.size != self.n_var_params: - raise ValueError(f"Expected {self.n_var_params} variational parameters but "\ + raise ValueError(f"Expected {self.n_var_params} variational parameters but " f"received {initial_var_params.size}.") self.var_params = initial_var_params return initial_var_params @@ -178,7 +178,7 @@ def prepare_reference_state(self): with the transform used to obtain the qubit operator. """ if self.default_reference_state not in self.supported_reference_state: - raise ValueError(f"Only supported reference state methods are: "\ + raise ValueError(f"Only supported reference state methods are: " f"{self.supported_reference_state}") if self.default_reference_state == "HF": reference_state_circuit = get_qmf_circuit(self.var_params, True) diff --git a/tangelo/toolboxes/ansatz_generator/rucc.py b/tangelo/toolboxes/ansatz_generator/rucc.py index edd2116b5..5fe4b5831 100644 --- a/tangelo/toolboxes/ansatz_generator/rucc.py +++ b/tangelo/toolboxes/ansatz_generator/rucc.py @@ -83,8 +83,8 @@ def set_var_params(self, var_params=None): else: initial_var_params = np.array(var_params) if initial_var_params.size != self.n_var_params: - raise ValueError(f"Expected {self.n_var_params} variational parameters but "\ - f"received {initial_var_params.size}.") + raise ValueError(f"Expected {self.n_var_params} variational parameters but " + f"received {initial_var_params.size}.") self.var_params = initial_var_params return initial_var_params diff --git a/tangelo/toolboxes/ansatz_generator/tests/__init__.py b/tangelo/toolboxes/ansatz_generator/tests/__init__.py index 81a799660..532746351 100644 --- a/tangelo/toolboxes/ansatz_generator/tests/__init__.py +++ b/tangelo/toolboxes/ansatz_generator/tests/__init__.py @@ -11,4 +11,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - diff --git a/tangelo/toolboxes/ansatz_generator/tests/test_qcc.py b/tangelo/toolboxes/ansatz_generator/tests/test_qcc.py index 3f7750d23..e52920eed 100644 --- a/tangelo/toolboxes/ansatz_generator/tests/test_qcc.py +++ b/tangelo/toolboxes/ansatz_generator/tests/test_qcc.py @@ -115,7 +115,7 @@ def test_qcc_h4_cation(self): QubitOperator("X0 Y2"), QubitOperator("X0 X1 Y3 X4 X5"), QubitOperator("X0 X1 X2 Y3 X4")] qcc_var_params = [ 0.26202301, -0.21102705, 0.11683144, -0.24234041, 0.13832747, - -0.0951985 , -0.03501809, 0.0640034 , 0.0542095] + -0.0951985, -0.03501809, 0.0640034, 0.0542095] qcc_ansatz = QCC(mol_H4_cation_sto3g, "SCBK", True, qcc_op_list) # Build a QMF + QCC circuit diff --git a/tangelo/toolboxes/ansatz_generator/tests/test_qmf.py b/tangelo/toolboxes/ansatz_generator/tests/test_qmf.py index 569a70969..0c0eea88a 100644 --- a/tangelo/toolboxes/ansatz_generator/tests/test_qmf.py +++ b/tangelo/toolboxes/ansatz_generator/tests/test_qmf.py @@ -65,7 +65,7 @@ def test_qmf_set_params_upper_hf_state_h2(): qmf_ansatz = QMF(mol_H2_sto3g) qmf_ansatz.set_var_params("HF_State") - expected = np.array([np.pi] * 2 + [0.] * 6) + expected = np.array([np.pi] * 2 + [0.] * 6) np.testing.assert_allclose(expected, qmf_ansatz.var_params, rtol=1e-10) @staticmethod @@ -75,7 +75,7 @@ def test_qmf_set_params_lower_hf_state_h2(): qmf_ansatz = QMF(mol_H2_sto3g) qmf_ansatz.set_var_params("hf_state") - expected = np.array([np.pi] * 2 + [0.] * 6) + expected = np.array([np.pi] * 2 + [0.] * 6) np.testing.assert_allclose(expected, qmf_ansatz.var_params, rtol=1e-10) @staticmethod @@ -85,14 +85,14 @@ def test_qmf_set_params_hf_state_h4(): qmf_ansatz = QMF(mol_H4_sto3g) qmf_ansatz.set_var_params("hf_state") - expected = np.array([np.pi] * 4 + [0.] * 12) + expected = np.array([np.pi] * 4 + [0.] * 12) np.testing.assert_allclose(expected, qmf_ansatz.var_params, rtol=1e-10) def test_qmf_closed_h2(self): """ Verify closed-shell QMF functionalities for H2 """ # Build ansatz and circuit - qmf_var_params = [np.pi] * 2 + [0.] * 6 + qmf_var_params = [np.pi] * 2 + [0.] * 6 qmf_ansatz = QMF(mol_H2_sto3g) qmf_ansatz.build_circuit() @@ -108,7 +108,7 @@ def test_qmf_closed_h4(self): """ Verify closed-shell QMF functionalities for H4. """ # Build ansatz and circuit - qmf_var_params = [np.pi] * 4 + [0.] * 12 + qmf_var_params = [np.pi] * 4 + [0.] * 12 qmf_ansatz = QMF(mol_H4_sto3g) qmf_ansatz.build_circuit() diff --git a/tangelo/toolboxes/ansatz_generator/tests/test_upccgsd.py b/tangelo/toolboxes/ansatz_generator/tests/test_upccgsd.py index 97d372330..01cf460eb 100644 --- a/tangelo/toolboxes/ansatz_generator/tests/test_upccgsd.py +++ b/tangelo/toolboxes/ansatz_generator/tests/test_upccgsd.py @@ -95,10 +95,10 @@ def test_upccgsd_H4_doublecation(self): var_params = [1.08956248, 1.08956247, 1.05305993, 1.05305993, 0.8799399, 0.8799399, 0.88616586, 0.88616586, 1.09532143, 1.09532143, 1.23586857, 1.23586857, - 1.09001216, 0.85772769, 1.28020861, 1.05820721, 0.9680792 , 1.01693601, + 1.09001216, 0.85772769, 1.28020861, 1.05820721, 0.9680792, 1.01693601, 0.68355852, 0.68355852, 1.30303827, 1.30303827, 0.74524063, 0.74524063, 0.36958813, 0.36958813, 1.37092805, 1.37092805, 0.92860293, 0.92860293, - 1.30296676, 0.5803438 , 1.42469953, 1.05666723, 0.86961358, 0.55347531] + 1.30296676, 0.5803438, 1.42469953, 1.05666723, 0.86961358, 0.55347531] # Build circuit upccgsd_ansatz = UpCCGSD(mol_H4_doublecation_minao) diff --git a/tangelo/toolboxes/ansatz_generator/uccsd.py b/tangelo/toolboxes/ansatz_generator/uccsd.py index 55c9452c0..8e5c26d8c 100644 --- a/tangelo/toolboxes/ansatz_generator/uccsd.py +++ b/tangelo/toolboxes/ansatz_generator/uccsd.py @@ -121,8 +121,8 @@ def set_var_params(self, var_params=None): else: initial_var_params = np.array(var_params) if initial_var_params.size != self.n_var_params: - raise ValueError(f"Expected {self.n_var_params} variational parameters but "\ - f"received {initial_var_params.size}.") + raise ValueError(f"Expected {self.n_var_params} variational parameters but " + f"received {initial_var_params.size}.") self.var_params = initial_var_params return initial_var_params diff --git a/tangelo/toolboxes/ansatz_generator/upccgsd.py b/tangelo/toolboxes/ansatz_generator/upccgsd.py index 415c44c9c..b78093777 100644 --- a/tangelo/toolboxes/ansatz_generator/upccgsd.py +++ b/tangelo/toolboxes/ansatz_generator/upccgsd.py @@ -102,8 +102,8 @@ def set_var_params(self, var_params=None): else: initial_var_params = np.array(var_params) if initial_var_params.size != self.n_var_params: - raise ValueError(f"Expected {self.n_var_params} variational parameters but "\ - f"received {initial_var_params.size}.") + raise ValueError(f"Expected {self.n_var_params} variational parameters but " + f"received {initial_var_params.size}.") self.var_params = initial_var_params return initial_var_params diff --git a/tangelo/toolboxes/ansatz_generator/variational_circuit.py b/tangelo/toolboxes/ansatz_generator/variational_circuit.py index 3bdd70089..eff75cd8b 100644 --- a/tangelo/toolboxes/ansatz_generator/variational_circuit.py +++ b/tangelo/toolboxes/ansatz_generator/variational_circuit.py @@ -63,8 +63,8 @@ def set_var_params(self, var_params=None): else: var_params = np.array(var_params) if var_params.size != self.n_var_params: - raise ValueError(f"Expected {self.n_var_params} variational parameters but "\ - f"received {var_params.size}.") + raise ValueError(f"Expected {self.n_var_params} variational parameters but " + f"received {var_params.size}.") self.var_params = var_params return var_params diff --git a/tangelo/toolboxes/measurements/tests/__init__.py b/tangelo/toolboxes/measurements/tests/__init__.py index 81a799660..532746351 100644 --- a/tangelo/toolboxes/measurements/tests/__init__.py +++ b/tangelo/toolboxes/measurements/tests/__init__.py @@ -11,4 +11,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - diff --git a/tangelo/toolboxes/molecular_computation/__init__.py b/tangelo/toolboxes/molecular_computation/__init__.py index 81a799660..532746351 100644 --- a/tangelo/toolboxes/molecular_computation/__init__.py +++ b/tangelo/toolboxes/molecular_computation/__init__.py @@ -11,4 +11,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - diff --git a/tangelo/toolboxes/molecular_computation/molecule.py b/tangelo/toolboxes/molecular_computation/molecule.py index fd962e95d..e152145b7 100644 --- a/tangelo/toolboxes/molecular_computation/molecule.py +++ b/tangelo/toolboxes/molecular_computation/molecule.py @@ -94,7 +94,7 @@ class Molecule: def __post_init__(self): self.xyz = atom_string_to_list(self.xyz) if isinstance(self.xyz, str) else self.xyz mol = self.to_pyscf(basis="sto-3g") - self.n_atoms = mol.natm + self.n_atoms = mol.natm self.n_electrons = mol.nelectron self.n_min_orbitals = mol.nao_nr() @@ -390,7 +390,7 @@ def energy_from_rdms(self, one_rdm, two_rdm): # Getting 2-body integrals in atomic and converting to molecular basis. two_electron_integrals = ao2mo.kernel(pyscf_mol.intor("int2e"), self.mean_field.mo_coeff) - two_electron_integrals = ao2mo.restore(1, two_electron_integrals, len(self.mean_field.mo_coeff)) + two_electron_integrals = ao2mo.restore(1, two_electron_integrals, len(self.mean_field.mo_coeff)) # PQRS convention in openfermion: # h[p,q]=\int \phi_p(x)* (T + V_{ext}) \phi_q(x) dx @@ -398,8 +398,11 @@ def energy_from_rdms(self, one_rdm, two_rdm): # The convention is not the same with PySCF integrals. So, a change is # made and reverse back after performing the truncation for frozen # orbitals. - two_electron_integrals = two_electron_integrals.transpose(0, 2, 3, 1) - core_offset, one_electron_integrals, two_electron_integrals = get_active_space_integrals(one_electron_integrals, two_electron_integrals, self.frozen_occupied, self.active_mos) + two_electron_integrals = two_electron_integrals.transpose(0, 2, 3, 1) + core_offset, one_electron_integrals, two_electron_integrals = get_active_space_integrals(one_electron_integrals, + two_electron_integrals, + self.frozen_occupied, + self.active_mos) two_electron_integrals = two_electron_integrals.transpose(0, 3, 1, 2) # Adding frozen electron contribution to core constant. diff --git a/tangelo/toolboxes/molecular_computation/tests/__init__.py b/tangelo/toolboxes/molecular_computation/tests/__init__.py index 81a799660..532746351 100644 --- a/tangelo/toolboxes/molecular_computation/tests/__init__.py +++ b/tangelo/toolboxes/molecular_computation/tests/__init__.py @@ -11,4 +11,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - diff --git a/tangelo/toolboxes/operators/tests/__init__.py b/tangelo/toolboxes/operators/tests/__init__.py index 81a799660..532746351 100644 --- a/tangelo/toolboxes/operators/tests/__init__.py +++ b/tangelo/toolboxes/operators/tests/__init__.py @@ -11,4 +11,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - diff --git a/tangelo/toolboxes/post_processing/tests/__init__.py b/tangelo/toolboxes/post_processing/tests/__init__.py index 81a799660..532746351 100644 --- a/tangelo/toolboxes/post_processing/tests/__init__.py +++ b/tangelo/toolboxes/post_processing/tests/__init__.py @@ -11,4 +11,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - diff --git a/tangelo/toolboxes/qubit_mappings/mapping_transform.py b/tangelo/toolboxes/qubit_mappings/mapping_transform.py index 7d51f9e68..b226a8737 100644 --- a/tangelo/toolboxes/qubit_mappings/mapping_transform.py +++ b/tangelo/toolboxes/qubit_mappings/mapping_transform.py @@ -66,7 +66,7 @@ def get_fermion_operator(operator): for term in operator: try: fermion_operator += FermionOperator(term, operator[term]) - except: + except Exception: raise TypeError("Operator terms are not formatted as valid input for FermionOperator type.") return fermion_operator diff --git a/tangelo/toolboxes/qubit_mappings/tests/__init__.py b/tangelo/toolboxes/qubit_mappings/tests/__init__.py index 81a799660..532746351 100644 --- a/tangelo/toolboxes/qubit_mappings/tests/__init__.py +++ b/tangelo/toolboxes/qubit_mappings/tests/__init__.py @@ -11,4 +11,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - diff --git a/tangelo/toolboxes/qubit_mappings/tests/test_bravyi_kitaev.py b/tangelo/toolboxes/qubit_mappings/tests/test_bravyi_kitaev.py index 900b58dfb..ab3d91748 100644 --- a/tangelo/toolboxes/qubit_mappings/tests/test_bravyi_kitaev.py +++ b/tangelo/toolboxes/qubit_mappings/tests/test_bravyi_kitaev.py @@ -41,7 +41,7 @@ def test_openfermion_equivalence(self): """Test that our wrapper returns the same result as openfermion's bare implementation of bravyi_kitaev. """ - #Instantiate simple non-trivial FermionOperator input + # Instantiate simple non-trivial FermionOperator input input_operator = FermionOperator(((0, 0), (1, 0), (2, 0), (12, 1))) input_operator += FermionOperator((13, 1), 0.2) n_qubits = 14 @@ -49,10 +49,11 @@ def test_openfermion_equivalence(self): tangelo_result = bravyi_kitaev(input_operator, n_qubits=n_qubits) openfermion_result = openfermion_bravyi_kitaev(input_operator, n_qubits=n_qubits) - #check that the number of terms is the same. - self.assertEqual(len(tangelo_result.terms), len(openfermion_result.terms), msg="Number of terms generated does not agree with openfermion implementation of Bravyi Kitaev.") + # check that the number of terms is the same. + self.assertEqual(len(tangelo_result.terms), len(openfermion_result.terms), msg="Number of terms generated does not agree" + "with openfermion implementation of Bravyi Kitaev.") - #check that the term coefficients are the same + # check that the term coefficients are the same for ti in tangelo_result.terms: factor = tangelo_result.terms[ti] openfermion_factor = openfermion_result.terms[ti] From daa349c988a39ee3dd3fb3191d81cf337583cbe0 Mon Sep 17 00:00:00 2001 From: AlexandreF-1qbit <76115575+AlexandreF-1qbit@users.noreply.github.com> Date: Wed, 15 Dec 2021 15:55:50 -0500 Subject: [PATCH 31/68] Misc fixes (#97) * Frozen orbitals fix for open-shell system + docs. * Saved H4 2+ Hamiltonians to remove random fails. * Added load_operator function to linq simulator tests. --- .../linq/tests/data/H2_qubit_hamiltonian.txt | 15 -- .../linq/tests/data/H4_qubit_hamiltonian.txt | 185 ----------------- tangelo/linq/tests/data/mol_H2_qubitham.data | 16 ++ tangelo/linq/tests/data/mol_H4_qubitham.data | 186 ++++++++++++++++++ tangelo/linq/tests/test_simulator.py | 18 +- ...mol_H4_doublecation_minao_qubitham_bk.data | 186 ++++++++++++++++++ ...doublecation_minao_qubitham_bk_updown.data | 186 ++++++++++++++++++ ...mol_H4_doublecation_minao_qubitham_jw.data | 186 ++++++++++++++++++ .../ansatz_generator/tests/test_hea.py | 7 +- .../ansatz_generator/tests/test_qcc.py | 13 +- .../ansatz_generator/tests/test_uccsd.py | 7 +- .../ansatz_generator/tests/test_upccgsd.py | 7 +- .../measurements/tests/test_measurements.py | 5 +- .../tests/test_qubit_terms_grouping.py | 9 +- .../molecular_computation/frozen_orbitals.py | 18 +- 15 files changed, 806 insertions(+), 238 deletions(-) delete mode 100755 tangelo/linq/tests/data/H2_qubit_hamiltonian.txt delete mode 100755 tangelo/linq/tests/data/H4_qubit_hamiltonian.txt create mode 100644 tangelo/linq/tests/data/mol_H2_qubitham.data create mode 100644 tangelo/linq/tests/data/mol_H4_qubitham.data create mode 100644 tangelo/toolboxes/ansatz_generator/tests/data/mol_H4_doublecation_minao_qubitham_bk.data create mode 100644 tangelo/toolboxes/ansatz_generator/tests/data/mol_H4_doublecation_minao_qubitham_bk_updown.data create mode 100644 tangelo/toolboxes/ansatz_generator/tests/data/mol_H4_doublecation_minao_qubitham_jw.data diff --git a/tangelo/linq/tests/data/H2_qubit_hamiltonian.txt b/tangelo/linq/tests/data/H2_qubit_hamiltonian.txt deleted file mode 100755 index a14c0263b..000000000 --- a/tangelo/linq/tests/data/H2_qubit_hamiltonian.txt +++ /dev/null @@ -1,15 +0,0 @@ --0.09883484730799699 () -0.17120123806595913 ((0, 'Z'),) -0.17120123806595913 ((1, 'Z'),) --0.22279639651093153 ((2, 'Z'),) --0.22279639651093153 ((3, 'Z'),) -0.16862327595071594 ((0, 'Z'), (1, 'Z')) -0.12054612740556855 ((0, 'Z'), (2, 'Z')) -0.16586801132367487 ((0, 'Z'), (3, 'Z')) -0.16586801132367487 ((1, 'Z'), (2, 'Z')) -0.12054612740556855 ((1, 'Z'), (3, 'Z')) -0.1743494875700707 ((2, 'Z'), (3, 'Z')) --0.04532188391810631 ((0, 'X'), (1, 'X'), (2, 'Y'), (3, 'Y')) -0.04532188391810631 ((0, 'X'), (1, 'Y'), (2, 'Y'), (3, 'X')) -0.04532188391810631 ((0, 'Y'), (1, 'X'), (2, 'X'), (3, 'Y')) --0.04532188391810631 ((0, 'Y'), (1, 'Y'), (2, 'X'), (3, 'X')) diff --git a/tangelo/linq/tests/data/H4_qubit_hamiltonian.txt b/tangelo/linq/tests/data/H4_qubit_hamiltonian.txt deleted file mode 100755 index 060ddb1c0..000000000 --- a/tangelo/linq/tests/data/H4_qubit_hamiltonian.txt +++ /dev/null @@ -1,185 +0,0 @@ --0.5864429644841618 () -0.19400432768831255 ((0, 'Z'),) -0.19400432768831244 ((1, 'Z'),) -0.009893101366460294 ((2, 'Z'),) -0.009893101366460183 ((3, 'Z'),) -0.00817109087811796 ((4, 'Z'),) -0.00817109087811796 ((5, 'Z'),) --0.2188288408181192 ((6, 'Z'),) --0.2188288408181192 ((7, 'Z'),) -0.1259950684949725 ((0, 'Z'), (1, 'Z')) --0.008152374637608008 ((0, 'X'), (1, 'Z'), (2, 'X')) --0.008152374637608008 ((0, 'Y'), (1, 'Z'), (2, 'Y')) -0.08405878904131187 ((0, 'Z'), (2, 'Z')) -0.11579286411890732 ((0, 'Z'), (3, 'Z')) -0.08368205704693019 ((0, 'Z'), (4, 'Z')) -0.11251814764414257 ((0, 'Z'), (5, 'Z')) -0.10238128212053105 ((0, 'Z'), (6, 'Z')) -0.12703718875370734 ((0, 'Z'), (7, 'Z')) -0.11579286411890732 ((1, 'Z'), (2, 'Z')) --0.008152374637608012 ((1, 'X'), (2, 'Z'), (3, 'X')) --0.008152374637608012 ((1, 'Y'), (2, 'Z'), (3, 'Y')) -0.08405878904131187 ((1, 'Z'), (3, 'Z')) -0.11251814764414257 ((1, 'Z'), (4, 'Z')) -0.08368205704693019 ((1, 'Z'), (5, 'Z')) -0.12703718875370734 ((1, 'Z'), (6, 'Z')) -0.10238128212053105 ((1, 'Z'), (7, 'Z')) -0.12065975352000909 ((2, 'Z'), (3, 'Z')) -0.09058633900157059 ((2, 'Z'), (4, 'Z')) -0.11904470144010436 ((2, 'Z'), (5, 'Z')) -0.08558472064472517 ((2, 'Z'), (6, 'Z')) -0.11718495447554314 ((2, 'Z'), (7, 'Z')) -0.11904470144010436 ((3, 'Z'), (4, 'Z')) -0.09058633900157059 ((3, 'Z'), (5, 'Z')) -0.11718495447554314 ((3, 'Z'), (6, 'Z')) -0.08558472064472517 ((3, 'Z'), (7, 'Z')) -0.12508828101233394 ((4, 'Z'), (5, 'Z')) -0.009273258265640627 ((4, 'X'), (5, 'Z'), (6, 'X')) -0.009273258265640627 ((4, 'Y'), (5, 'Z'), (6, 'Y')) -0.08339319573976225 ((4, 'Z'), (6, 'Z')) -0.11277758956738607 ((4, 'Z'), (7, 'Z')) -0.11277758956738607 ((5, 'Z'), (6, 'Z')) -0.009273258265640627 ((5, 'X'), (6, 'Z'), (7, 'X')) -0.009273258265640627 ((5, 'Y'), (6, 'Z'), (7, 'Y')) -0.08339319573976225 ((5, 'Z'), (7, 'Z')) -0.1383208729101724 ((6, 'Z'), (7, 'Z')) -0.013364563894405072 ((0, 'Z'), (1, 'X'), (2, 'Z'), (3, 'X')) -0.013364563894405072 ((0, 'Z'), (1, 'Y'), (2, 'Z'), (3, 'Y')) -0.01336456389440507 ((0, 'X'), (2, 'X')) -0.01336456389440507 ((0, 'Y'), (2, 'Y')) --0.03173407507759546 ((0, 'X'), (1, 'X'), (2, 'Y'), (3, 'Y')) -0.03173407507759546 ((0, 'X'), (1, 'Y'), (2, 'Y'), (3, 'X')) -0.03173407507759546 ((0, 'Y'), (1, 'X'), (2, 'X'), (3, 'Y')) --0.03173407507759546 ((0, 'Y'), (1, 'Y'), (2, 'X'), (3, 'X')) --0.028836090597212385 ((0, 'X'), (1, 'X'), (4, 'Y'), (5, 'Y')) -0.028836090597212385 ((0, 'X'), (1, 'Y'), (4, 'Y'), (5, 'X')) -0.028836090597212385 ((0, 'Y'), (1, 'X'), (4, 'X'), (5, 'Y')) --0.028836090597212385 ((0, 'Y'), (1, 'Y'), (4, 'X'), (5, 'X')) -0.008803019044167481 ((0, 'X'), (1, 'X'), (4, 'Y'), (5, 'Z'), (6, 'Z'), (7, 'Y')) --0.008803019044167481 ((0, 'X'), (1, 'Y'), (4, 'Y'), (5, 'Z'), (6, 'Z'), (7, 'X')) --0.008803019044167481 ((0, 'Y'), (1, 'X'), (4, 'X'), (5, 'Z'), (6, 'Z'), (7, 'Y')) -0.008803019044167481 ((0, 'Y'), (1, 'Y'), (4, 'X'), (5, 'Z'), (6, 'Z'), (7, 'X')) -0.008803019044167481 ((0, 'X'), (1, 'X'), (5, 'X'), (6, 'X')) -0.008803019044167481 ((0, 'X'), (1, 'Y'), (5, 'Y'), (6, 'X')) -0.008803019044167481 ((0, 'Y'), (1, 'X'), (5, 'X'), (6, 'Y')) -0.008803019044167481 ((0, 'Y'), (1, 'Y'), (5, 'Y'), (6, 'Y')) --0.024655906633176294 ((0, 'X'), (1, 'X'), (6, 'Y'), (7, 'Y')) -0.024655906633176294 ((0, 'X'), (1, 'Y'), (6, 'Y'), (7, 'X')) -0.024655906633176294 ((0, 'Y'), (1, 'X'), (6, 'X'), (7, 'Y')) --0.024655906633176294 ((0, 'Y'), (1, 'Y'), (6, 'X'), (7, 'X')) -0.013296397151725526 ((0, 'X'), (1, 'Z'), (2, 'X'), (4, 'X'), (5, 'Z'), (6, 'X')) -0.00046953113589866 ((0, 'X'), (1, 'Z'), (2, 'X'), (4, 'Y'), (5, 'Z'), (6, 'Y')) -0.012826866015826863 ((0, 'X'), (1, 'Z'), (2, 'Y'), (4, 'Y'), (5, 'Z'), (6, 'X')) -0.012826866015826863 ((0, 'Y'), (1, 'Z'), (2, 'X'), (4, 'X'), (5, 'Z'), (6, 'Y')) -0.00046953113589866 ((0, 'Y'), (1, 'Z'), (2, 'Y'), (4, 'X'), (5, 'Z'), (6, 'X')) -0.013296397151725526 ((0, 'Y'), (1, 'Z'), (2, 'Y'), (4, 'Y'), (5, 'Z'), (6, 'Y')) --0.004603432481434463 ((0, 'X'), (1, 'Z'), (2, 'X'), (3, 'Z')) --0.004603432481434463 ((0, 'Y'), (1, 'Z'), (2, 'Y'), (3, 'Z')) --0.016027079159580276 ((0, 'X'), (1, 'Z'), (2, 'Z'), (3, 'X'), (4, 'Y'), (5, 'Y')) -0.016027079159580276 ((0, 'X'), (1, 'Z'), (2, 'Z'), (3, 'Y'), (4, 'Y'), (5, 'X')) -0.016027079159580276 ((0, 'Y'), (1, 'Z'), (2, 'Z'), (3, 'X'), (4, 'X'), (5, 'Y')) --0.016027079159580276 ((0, 'Y'), (1, 'Z'), (2, 'Z'), (3, 'Y'), (4, 'X'), (5, 'X')) --0.028434833328258968 ((0, 'X'), (1, 'Z'), (2, 'Z'), (3, 'X'), (4, 'Y'), (5, 'Z'), (6, 'Z'), (7, 'Y')) -0.028434833328258968 ((0, 'X'), (1, 'Z'), (2, 'Z'), (3, 'Y'), (4, 'Y'), (5, 'Z'), (6, 'Z'), (7, 'X')) -0.028434833328258968 ((0, 'Y'), (1, 'Z'), (2, 'Z'), (3, 'X'), (4, 'X'), (5, 'Z'), (6, 'Z'), (7, 'Y')) --0.028434833328258968 ((0, 'Y'), (1, 'Z'), (2, 'Z'), (3, 'Y'), (4, 'X'), (5, 'Z'), (6, 'Z'), (7, 'X')) --0.015607967312432098 ((0, 'X'), (1, 'Z'), (2, 'Z'), (3, 'X'), (5, 'X'), (6, 'X')) --0.015607967312432098 ((0, 'X'), (1, 'Z'), (2, 'Z'), (3, 'Y'), (5, 'Y'), (6, 'X')) --0.015607967312432098 ((0, 'Y'), (1, 'Z'), (2, 'Z'), (3, 'X'), (5, 'X'), (6, 'Y')) --0.015607967312432098 ((0, 'Y'), (1, 'Z'), (2, 'Z'), (3, 'Y'), (5, 'Y'), (6, 'Y')) -0.013015580846970284 ((0, 'X'), (1, 'Z'), (2, 'Z'), (3, 'X'), (6, 'Y'), (7, 'Y')) --0.013015580846970284 ((0, 'X'), (1, 'Z'), (2, 'Z'), (3, 'Y'), (6, 'Y'), (7, 'X')) --0.013015580846970284 ((0, 'Y'), (1, 'Z'), (2, 'Z'), (3, 'X'), (6, 'X'), (7, 'Y')) -0.013015580846970284 ((0, 'Y'), (1, 'Z'), (2, 'Z'), (3, 'Y'), (6, 'X'), (7, 'X')) -0.0031901683358653275 ((0, 'Z'), (4, 'X'), (5, 'Z'), (6, 'X')) -0.0031901683358653275 ((0, 'Z'), (4, 'Y'), (5, 'Z'), (6, 'Y')) -0.0032535295665562097 ((0, 'X'), (1, 'Z'), (2, 'X'), (4, 'Z')) -0.0032535295665562097 ((0, 'Y'), (1, 'Z'), (2, 'Y'), (4, 'Z')) -0.01199318738003281 ((0, 'Z'), (5, 'X'), (6, 'Z'), (7, 'X')) -0.01199318738003281 ((0, 'Z'), (5, 'Y'), (6, 'Z'), (7, 'Y')) --0.012773549593024066 ((0, 'X'), (1, 'Z'), (2, 'X'), (5, 'Z')) --0.012773549593024066 ((0, 'Y'), (1, 'Z'), (2, 'Y'), (5, 'Z')) -0.028904364464157624 ((0, 'X'), (1, 'Z'), (2, 'X'), (5, 'X'), (6, 'Z'), (7, 'X')) -0.028904364464157624 ((0, 'X'), (1, 'Z'), (2, 'X'), (5, 'Y'), (6, 'Z'), (7, 'Y')) -0.028904364464157624 ((0, 'Y'), (1, 'Z'), (2, 'Y'), (5, 'X'), (6, 'Z'), (7, 'X')) -0.028904364464157624 ((0, 'Y'), (1, 'Z'), (2, 'Y'), (5, 'Y'), (6, 'Z'), (7, 'Y')) -0.0067089726150380965 ((0, 'X'), (1, 'Z'), (2, 'X'), (6, 'Z')) -0.0067089726150380965 ((0, 'Y'), (1, 'Z'), (2, 'Y'), (6, 'Z')) -0.019724553462008382 ((0, 'X'), (1, 'Z'), (2, 'X'), (7, 'Z')) -0.019724553462008382 ((0, 'Y'), (1, 'Z'), (2, 'Y'), (7, 'Z')) --0.004603432481434463 ((1, 'X'), (3, 'X')) --0.004603432481434463 ((1, 'Y'), (3, 'Y')) --0.016027079159580276 ((1, 'X'), (2, 'X'), (4, 'X'), (5, 'X')) --0.016027079159580276 ((1, 'X'), (2, 'Y'), (4, 'Y'), (5, 'X')) --0.016027079159580276 ((1, 'Y'), (2, 'X'), (4, 'X'), (5, 'Y')) --0.016027079159580276 ((1, 'Y'), (2, 'Y'), (4, 'Y'), (5, 'Y')) --0.015607967312432098 ((1, 'X'), (2, 'X'), (4, 'X'), (5, 'Z'), (6, 'Z'), (7, 'X')) --0.015607967312432098 ((1, 'X'), (2, 'Y'), (4, 'Y'), (5, 'Z'), (6, 'Z'), (7, 'X')) --0.015607967312432098 ((1, 'Y'), (2, 'X'), (4, 'X'), (5, 'Z'), (6, 'Z'), (7, 'Y')) --0.015607967312432098 ((1, 'Y'), (2, 'Y'), (4, 'Y'), (5, 'Z'), (6, 'Z'), (7, 'Y')) --0.028434833328258968 ((1, 'X'), (2, 'X'), (5, 'Y'), (6, 'Y')) -0.028434833328258968 ((1, 'X'), (2, 'Y'), (5, 'Y'), (6, 'X')) -0.028434833328258968 ((1, 'Y'), (2, 'X'), (5, 'X'), (6, 'Y')) --0.028434833328258968 ((1, 'Y'), (2, 'Y'), (5, 'X'), (6, 'X')) -0.013015580846970284 ((1, 'X'), (2, 'X'), (6, 'X'), (7, 'X')) -0.013015580846970284 ((1, 'X'), (2, 'Y'), (6, 'Y'), (7, 'X')) -0.013015580846970284 ((1, 'Y'), (2, 'X'), (6, 'X'), (7, 'Y')) -0.013015580846970284 ((1, 'Y'), (2, 'Y'), (6, 'Y'), (7, 'Y')) -0.013296397151725526 ((1, 'X'), (2, 'Z'), (3, 'X'), (5, 'X'), (6, 'Z'), (7, 'X')) -0.00046953113589866 ((1, 'X'), (2, 'Z'), (3, 'X'), (5, 'Y'), (6, 'Z'), (7, 'Y')) -0.012826866015826863 ((1, 'X'), (2, 'Z'), (3, 'Y'), (5, 'Y'), (6, 'Z'), (7, 'X')) -0.012826866015826863 ((1, 'Y'), (2, 'Z'), (3, 'X'), (5, 'X'), (6, 'Z'), (7, 'Y')) -0.00046953113589866 ((1, 'Y'), (2, 'Z'), (3, 'Y'), (5, 'X'), (6, 'Z'), (7, 'X')) -0.013296397151725526 ((1, 'Y'), (2, 'Z'), (3, 'Y'), (5, 'Y'), (6, 'Z'), (7, 'Y')) -0.01199318738003281 ((1, 'Z'), (4, 'X'), (5, 'Z'), (6, 'X')) -0.01199318738003281 ((1, 'Z'), (4, 'Y'), (5, 'Z'), (6, 'Y')) --0.012773549593024066 ((1, 'X'), (2, 'Z'), (3, 'X'), (4, 'Z')) --0.012773549593024066 ((1, 'Y'), (2, 'Z'), (3, 'Y'), (4, 'Z')) -0.028904364464157624 ((1, 'X'), (2, 'Z'), (3, 'X'), (4, 'X'), (5, 'Z'), (6, 'X')) -0.028904364464157624 ((1, 'X'), (2, 'Z'), (3, 'X'), (4, 'Y'), (5, 'Z'), (6, 'Y')) -0.028904364464157624 ((1, 'Y'), (2, 'Z'), (3, 'Y'), (4, 'X'), (5, 'Z'), (6, 'X')) -0.028904364464157624 ((1, 'Y'), (2, 'Z'), (3, 'Y'), (4, 'Y'), (5, 'Z'), (6, 'Y')) -0.0031901683358653275 ((1, 'Z'), (5, 'X'), (6, 'Z'), (7, 'X')) -0.0031901683358653275 ((1, 'Z'), (5, 'Y'), (6, 'Z'), (7, 'Y')) -0.0032535295665562097 ((1, 'X'), (2, 'Z'), (3, 'X'), (5, 'Z')) -0.0032535295665562097 ((1, 'Y'), (2, 'Z'), (3, 'Y'), (5, 'Z')) -0.019724553462008382 ((1, 'X'), (2, 'Z'), (3, 'X'), (6, 'Z')) -0.019724553462008382 ((1, 'Y'), (2, 'Z'), (3, 'Y'), (6, 'Z')) -0.0067089726150380965 ((1, 'X'), (2, 'Z'), (3, 'X'), (7, 'Z')) -0.0067089726150380965 ((1, 'Y'), (2, 'Z'), (3, 'Y'), (7, 'Z')) --0.028458362438533766 ((2, 'X'), (3, 'X'), (4, 'Y'), (5, 'Y')) -0.028458362438533766 ((2, 'X'), (3, 'Y'), (4, 'Y'), (5, 'X')) -0.028458362438533766 ((2, 'Y'), (3, 'X'), (4, 'X'), (5, 'Y')) --0.028458362438533766 ((2, 'Y'), (3, 'Y'), (4, 'X'), (5, 'X')) --0.012803045265112938 ((2, 'X'), (3, 'X'), (4, 'Y'), (5, 'Z'), (6, 'Z'), (7, 'Y')) -0.012803045265112938 ((2, 'X'), (3, 'Y'), (4, 'Y'), (5, 'Z'), (6, 'Z'), (7, 'X')) -0.012803045265112938 ((2, 'Y'), (3, 'X'), (4, 'X'), (5, 'Z'), (6, 'Z'), (7, 'Y')) --0.012803045265112938 ((2, 'Y'), (3, 'Y'), (4, 'X'), (5, 'Z'), (6, 'Z'), (7, 'X')) --0.012803045265112936 ((2, 'X'), (3, 'X'), (5, 'X'), (6, 'X')) --0.012803045265112936 ((2, 'X'), (3, 'Y'), (5, 'Y'), (6, 'X')) --0.012803045265112936 ((2, 'Y'), (3, 'X'), (5, 'X'), (6, 'Y')) --0.012803045265112936 ((2, 'Y'), (3, 'Y'), (5, 'Y'), (6, 'Y')) --0.03160023383081798 ((2, 'X'), (3, 'X'), (6, 'Y'), (7, 'Y')) -0.03160023383081798 ((2, 'X'), (3, 'Y'), (6, 'Y'), (7, 'X')) -0.03160023383081798 ((2, 'Y'), (3, 'X'), (6, 'X'), (7, 'Y')) --0.03160023383081798 ((2, 'Y'), (3, 'Y'), (6, 'X'), (7, 'X')) -0.006438934802049977 ((2, 'Z'), (4, 'X'), (5, 'Z'), (6, 'X')) -0.006438934802049977 ((2, 'Z'), (4, 'Y'), (5, 'Z'), (6, 'Y')) --0.00636411046306296 ((2, 'Z'), (5, 'X'), (6, 'Z'), (7, 'X')) --0.00636411046306296 ((2, 'Z'), (5, 'Y'), (6, 'Z'), (7, 'Y')) --0.00636411046306296 ((3, 'Z'), (4, 'X'), (5, 'Z'), (6, 'X')) --0.00636411046306296 ((3, 'Z'), (4, 'Y'), (5, 'Z'), (6, 'Y')) -0.006438934802049977 ((3, 'Z'), (5, 'X'), (6, 'Z'), (7, 'X')) -0.006438934802049977 ((3, 'Z'), (5, 'Y'), (6, 'Z'), (7, 'Y')) --0.012616232733648354 ((4, 'Z'), (5, 'X'), (6, 'Z'), (7, 'X')) --0.012616232733648354 ((4, 'Z'), (5, 'Y'), (6, 'Z'), (7, 'Y')) --0.012616232733648354 ((4, 'X'), (6, 'X')) --0.012616232733648354 ((4, 'Y'), (6, 'Y')) --0.029384393827623808 ((4, 'X'), (5, 'X'), (6, 'Y'), (7, 'Y')) -0.029384393827623808 ((4, 'X'), (5, 'Y'), (6, 'Y'), (7, 'X')) -0.029384393827623808 ((4, 'Y'), (5, 'X'), (6, 'X'), (7, 'Y')) --0.029384393827623808 ((4, 'Y'), (5, 'Y'), (6, 'X'), (7, 'X')) -0.018601154522892904 ((4, 'X'), (5, 'Z'), (6, 'X'), (7, 'Z')) -0.018601154522892904 ((4, 'Y'), (5, 'Z'), (6, 'Y'), (7, 'Z')) -0.018601154522892904 ((5, 'X'), (7, 'X')) -0.018601154522892904 ((5, 'Y'), (7, 'Y')) diff --git a/tangelo/linq/tests/data/mol_H2_qubitham.data b/tangelo/linq/tests/data/mol_H2_qubitham.data new file mode 100644 index 000000000..707028b51 --- /dev/null +++ b/tangelo/linq/tests/data/mol_H2_qubitham.data @@ -0,0 +1,16 @@ +QubitOperator: +-0.09883484730799699 [] + +-0.04532188391810631 [X0 X1 Y2 Y3] + +0.04532188391810631 [X0 Y1 Y2 X3] + +0.04532188391810631 [Y0 X1 X2 Y3] + +-0.04532188391810631 [Y0 Y1 X2 X3] + +0.17120123806595913 [Z0] + +0.16862327595071594 [Z0 Z1] + +0.12054612740556855 [Z0 Z2] + +0.16586801132367487 [Z0 Z3] + +0.17120123806595913 [Z1] + +0.16586801132367487 [Z1 Z2] + +0.12054612740556855 [Z1 Z3] + +-0.22279639651093153 [Z2] + +0.1743494875700707 [Z2 Z3] + +-0.22279639651093153 [Z3] \ No newline at end of file diff --git a/tangelo/linq/tests/data/mol_H4_qubitham.data b/tangelo/linq/tests/data/mol_H4_qubitham.data new file mode 100644 index 000000000..1a42e6619 --- /dev/null +++ b/tangelo/linq/tests/data/mol_H4_qubitham.data @@ -0,0 +1,186 @@ +QubitOperator: +-0.5864429644841618 [] + +-0.03173407507759546 [X0 X1 Y2 Y3] + +-0.028836090597212385 [X0 X1 Y4 Y5] + +0.008803019044167481 [X0 X1 Y4 Z5 Z6 Y7] + +0.008803019044167481 [X0 X1 X5 X6] + +-0.024655906633176294 [X0 X1 Y6 Y7] + +0.03173407507759546 [X0 Y1 Y2 X3] + +0.028836090597212385 [X0 Y1 Y4 X5] + +-0.008803019044167481 [X0 Y1 Y4 Z5 Z6 X7] + +0.008803019044167481 [X0 Y1 Y5 X6] + +0.024655906633176294 [X0 Y1 Y6 X7] + +-0.008152374637608008 [X0 Z1 X2] + +-0.004603432481434463 [X0 Z1 X2 Z3] + +0.013296397151725526 [X0 Z1 X2 X4 Z5 X6] + +0.00046953113589866 [X0 Z1 X2 Y4 Z5 Y6] + +0.0032535295665562097 [X0 Z1 X2 Z4] + +0.028904364464157624 [X0 Z1 X2 X5 Z6 X7] + +0.028904364464157624 [X0 Z1 X2 Y5 Z6 Y7] + +-0.012773549593024066 [X0 Z1 X2 Z5] + +0.0067089726150380965 [X0 Z1 X2 Z6] + +0.019724553462008382 [X0 Z1 X2 Z7] + +0.012826866015826863 [X0 Z1 Y2 Y4 Z5 X6] + +-0.016027079159580276 [X0 Z1 Z2 X3 Y4 Y5] + +-0.028434833328258968 [X0 Z1 Z2 X3 Y4 Z5 Z6 Y7] + +-0.015607967312432098 [X0 Z1 Z2 X3 X5 X6] + +0.013015580846970284 [X0 Z1 Z2 X3 Y6 Y7] + +0.016027079159580276 [X0 Z1 Z2 Y3 Y4 X5] + +0.028434833328258968 [X0 Z1 Z2 Y3 Y4 Z5 Z6 X7] + +-0.015607967312432098 [X0 Z1 Z2 Y3 Y5 X6] + +-0.013015580846970284 [X0 Z1 Z2 Y3 Y6 X7] + +0.01336456389440507 [X0 X2] + +0.03173407507759546 [Y0 X1 X2 Y3] + +0.028836090597212385 [Y0 X1 X4 Y5] + +-0.008803019044167481 [Y0 X1 X4 Z5 Z6 Y7] + +0.008803019044167481 [Y0 X1 X5 Y6] + +0.024655906633176294 [Y0 X1 X6 Y7] + +-0.03173407507759546 [Y0 Y1 X2 X3] + +-0.028836090597212385 [Y0 Y1 X4 X5] + +0.008803019044167481 [Y0 Y1 X4 Z5 Z6 X7] + +0.008803019044167481 [Y0 Y1 Y5 Y6] + +-0.024655906633176294 [Y0 Y1 X6 X7] + +0.012826866015826863 [Y0 Z1 X2 X4 Z5 Y6] + +-0.008152374637608008 [Y0 Z1 Y2] + +-0.004603432481434463 [Y0 Z1 Y2 Z3] + +0.00046953113589866 [Y0 Z1 Y2 X4 Z5 X6] + +0.013296397151725526 [Y0 Z1 Y2 Y4 Z5 Y6] + +0.0032535295665562097 [Y0 Z1 Y2 Z4] + +0.028904364464157624 [Y0 Z1 Y2 X5 Z6 X7] + +0.028904364464157624 [Y0 Z1 Y2 Y5 Z6 Y7] + +-0.012773549593024066 [Y0 Z1 Y2 Z5] + +0.0067089726150380965 [Y0 Z1 Y2 Z6] + +0.019724553462008382 [Y0 Z1 Y2 Z7] + +0.016027079159580276 [Y0 Z1 Z2 X3 X4 Y5] + +0.028434833328258968 [Y0 Z1 Z2 X3 X4 Z5 Z6 Y7] + +-0.015607967312432098 [Y0 Z1 Z2 X3 X5 Y6] + +-0.013015580846970284 [Y0 Z1 Z2 X3 X6 Y7] + +-0.016027079159580276 [Y0 Z1 Z2 Y3 X4 X5] + +-0.028434833328258968 [Y0 Z1 Z2 Y3 X4 Z5 Z6 X7] + +-0.015607967312432098 [Y0 Z1 Z2 Y3 Y5 Y6] + +0.013015580846970284 [Y0 Z1 Z2 Y3 X6 X7] + +0.01336456389440507 [Y0 Y2] + +0.19400432768831255 [Z0] + +0.013364563894405072 [Z0 X1 Z2 X3] + +0.013364563894405072 [Z0 Y1 Z2 Y3] + +0.1259950684949725 [Z0 Z1] + +0.08405878904131187 [Z0 Z2] + +0.11579286411890732 [Z0 Z3] + +0.0031901683358653275 [Z0 X4 Z5 X6] + +0.0031901683358653275 [Z0 Y4 Z5 Y6] + +0.08368205704693019 [Z0 Z4] + +0.01199318738003281 [Z0 X5 Z6 X7] + +0.01199318738003281 [Z0 Y5 Z6 Y7] + +0.11251814764414257 [Z0 Z5] + +0.10238128212053105 [Z0 Z6] + +0.12703718875370734 [Z0 Z7] + +-0.016027079159580276 [X1 X2 X4 X5] + +-0.015607967312432098 [X1 X2 X4 Z5 Z6 X7] + +-0.028434833328258968 [X1 X2 Y5 Y6] + +0.013015580846970284 [X1 X2 X6 X7] + +-0.016027079159580276 [X1 Y2 Y4 X5] + +-0.015607967312432098 [X1 Y2 Y4 Z5 Z6 X7] + +0.028434833328258968 [X1 Y2 Y5 X6] + +0.013015580846970284 [X1 Y2 Y6 X7] + +-0.008152374637608012 [X1 Z2 X3] + +0.028904364464157624 [X1 Z2 X3 X4 Z5 X6] + +0.028904364464157624 [X1 Z2 X3 Y4 Z5 Y6] + +-0.012773549593024066 [X1 Z2 X3 Z4] + +0.013296397151725526 [X1 Z2 X3 X5 Z6 X7] + +0.00046953113589866 [X1 Z2 X3 Y5 Z6 Y7] + +0.0032535295665562097 [X1 Z2 X3 Z5] + +0.019724553462008382 [X1 Z2 X3 Z6] + +0.0067089726150380965 [X1 Z2 X3 Z7] + +0.012826866015826863 [X1 Z2 Y3 Y5 Z6 X7] + +-0.004603432481434463 [X1 X3] + +-0.016027079159580276 [Y1 X2 X4 Y5] + +-0.015607967312432098 [Y1 X2 X4 Z5 Z6 Y7] + +0.028434833328258968 [Y1 X2 X5 Y6] + +0.013015580846970284 [Y1 X2 X6 Y7] + +-0.016027079159580276 [Y1 Y2 Y4 Y5] + +-0.015607967312432098 [Y1 Y2 Y4 Z5 Z6 Y7] + +-0.028434833328258968 [Y1 Y2 X5 X6] + +0.013015580846970284 [Y1 Y2 Y6 Y7] + +0.012826866015826863 [Y1 Z2 X3 X5 Z6 Y7] + +-0.008152374637608012 [Y1 Z2 Y3] + +0.028904364464157624 [Y1 Z2 Y3 X4 Z5 X6] + +0.028904364464157624 [Y1 Z2 Y3 Y4 Z5 Y6] + +-0.012773549593024066 [Y1 Z2 Y3 Z4] + +0.00046953113589866 [Y1 Z2 Y3 X5 Z6 X7] + +0.013296397151725526 [Y1 Z2 Y3 Y5 Z6 Y7] + +0.0032535295665562097 [Y1 Z2 Y3 Z5] + +0.019724553462008382 [Y1 Z2 Y3 Z6] + +0.0067089726150380965 [Y1 Z2 Y3 Z7] + +-0.004603432481434463 [Y1 Y3] + +0.19400432768831244 [Z1] + +0.11579286411890732 [Z1 Z2] + +0.08405878904131187 [Z1 Z3] + +0.01199318738003281 [Z1 X4 Z5 X6] + +0.01199318738003281 [Z1 Y4 Z5 Y6] + +0.11251814764414257 [Z1 Z4] + +0.0031901683358653275 [Z1 X5 Z6 X7] + +0.0031901683358653275 [Z1 Y5 Z6 Y7] + +0.08368205704693019 [Z1 Z5] + +0.12703718875370734 [Z1 Z6] + +0.10238128212053105 [Z1 Z7] + +-0.028458362438533766 [X2 X3 Y4 Y5] + +-0.012803045265112938 [X2 X3 Y4 Z5 Z6 Y7] + +-0.012803045265112936 [X2 X3 X5 X6] + +-0.03160023383081798 [X2 X3 Y6 Y7] + +0.028458362438533766 [X2 Y3 Y4 X5] + +0.012803045265112938 [X2 Y3 Y4 Z5 Z6 X7] + +-0.012803045265112936 [X2 Y3 Y5 X6] + +0.03160023383081798 [X2 Y3 Y6 X7] + +0.028458362438533766 [Y2 X3 X4 Y5] + +0.012803045265112938 [Y2 X3 X4 Z5 Z6 Y7] + +-0.012803045265112936 [Y2 X3 X5 Y6] + +0.03160023383081798 [Y2 X3 X6 Y7] + +-0.028458362438533766 [Y2 Y3 X4 X5] + +-0.012803045265112938 [Y2 Y3 X4 Z5 Z6 X7] + +-0.012803045265112936 [Y2 Y3 Y5 Y6] + +-0.03160023383081798 [Y2 Y3 X6 X7] + +0.009893101366460294 [Z2] + +0.12065975352000909 [Z2 Z3] + +0.006438934802049977 [Z2 X4 Z5 X6] + +0.006438934802049977 [Z2 Y4 Z5 Y6] + +0.09058633900157059 [Z2 Z4] + +-0.00636411046306296 [Z2 X5 Z6 X7] + +-0.00636411046306296 [Z2 Y5 Z6 Y7] + +0.11904470144010436 [Z2 Z5] + +0.08558472064472517 [Z2 Z6] + +0.11718495447554314 [Z2 Z7] + +0.009893101366460183 [Z3] + +-0.00636411046306296 [Z3 X4 Z5 X6] + +-0.00636411046306296 [Z3 Y4 Z5 Y6] + +0.11904470144010436 [Z3 Z4] + +0.006438934802049977 [Z3 X5 Z6 X7] + +0.006438934802049977 [Z3 Y5 Z6 Y7] + +0.09058633900157059 [Z3 Z5] + +0.11718495447554314 [Z3 Z6] + +0.08558472064472517 [Z3 Z7] + +-0.029384393827623808 [X4 X5 Y6 Y7] + +0.029384393827623808 [X4 Y5 Y6 X7] + +0.009273258265640627 [X4 Z5 X6] + +0.018601154522892904 [X4 Z5 X6 Z7] + +-0.012616232733648354 [X4 X6] + +0.029384393827623808 [Y4 X5 X6 Y7] + +-0.029384393827623808 [Y4 Y5 X6 X7] + +0.009273258265640627 [Y4 Z5 Y6] + +0.018601154522892904 [Y4 Z5 Y6 Z7] + +-0.012616232733648354 [Y4 Y6] + +0.00817109087811796 [Z4] + +-0.012616232733648354 [Z4 X5 Z6 X7] + +-0.012616232733648354 [Z4 Y5 Z6 Y7] + +0.12508828101233394 [Z4 Z5] + +0.08339319573976225 [Z4 Z6] + +0.11277758956738607 [Z4 Z7] + +0.009273258265640627 [X5 Z6 X7] + +0.018601154522892904 [X5 X7] + +0.009273258265640627 [Y5 Z6 Y7] + +0.018601154522892904 [Y5 Y7] + +0.00817109087811796 [Z5] + +0.11277758956738607 [Z5 Z6] + +0.08339319573976225 [Z5 Z7] + +-0.2188288408181192 [Z6] + +0.1383208729101724 [Z6 Z7] + +-0.2188288408181192 [Z7] \ No newline at end of file diff --git a/tangelo/linq/tests/test_simulator.py b/tangelo/linq/tests/test_simulator.py index c247d6164..632bdb888 100644 --- a/tangelo/linq/tests/test_simulator.py +++ b/tangelo/linq/tests/test_simulator.py @@ -22,6 +22,7 @@ import time import numpy as np from openfermion.ops import QubitOperator +from openfermion import load_operator from tangelo.linq import Gate, Circuit, translator, Simulator from tangelo.linq.gate import PARAMETERIZED_GATES @@ -203,9 +204,7 @@ def test_get_exp_value_from_statevector_h2(self): """ Get expectation value of large circuits and qubit Hamiltonians corresponding to molecules. Molecule: H2 sto-3g = [("H", (0., 0., 0.)), ("H", (0., 0., 0.741377))] """ - with open(f"{path_data}/H2_qubit_hamiltonian.txt", "r") as ham_handle: - string_ham = ham_handle.read() - qubit_operator = string_ham_to_of(string_ham) + qubit_operator = load_operator("mol_H2_qubitham.data", data_directory=path_data, plain_text=True) with open(f"{path_data}/H2_UCCSD.qasm", "r") as circ_handle: openqasm_circ = circ_handle.read() @@ -234,9 +233,7 @@ def test_get_exp_value_from_initial_statevector_h2(self): Molecule: H2 sto-3g = [("H", (0., 0., 0.)), ("H", (0., 0., 0.741377))] Generate statevector first and then get_expectation value from statevector and empty circuit. """ - with open(f"{path_data}/H2_qubit_hamiltonian.txt", "r") as ham_handle: - string_ham = ham_handle.read() - qubit_operator = string_ham_to_of(string_ham) + qubit_operator = load_operator("mol_H2_qubitham.data", data_directory=path_data, plain_text=True) with open(f"{path_data}/H2_UCCSD.qasm", "r") as circ_handle: openqasm_circ = circ_handle.read() @@ -270,10 +267,8 @@ def test_get_exp_value_from_statevector_h4(self): ['H', [-1.0071067811865476, 0.0, 0.0]], ['H', [0.0, -1.0071067811865476, 0.0]]] """ + qubit_operator = load_operator("mol_H4_qubitham.data", data_directory=path_data, plain_text=True) - with open(f"{path_data}/H4_qubit_hamiltonian.txt", "r") as ham_handle: - string_ham = ham_handle.read() - qubit_operator = string_ham_to_of(string_ham) with open(f"{path_data}/H4_UCCSD.qasm", "r") as circ_handle: openqasm_circ = circ_handle.read() @@ -303,9 +298,8 @@ def test_get_exp_value_from_statevector_with_shots_h2(self): The result is computed using samples ("shots") drawn form a statevector simulator here. This is the kind of results we could expect from a noiseless QPU. """ - with open(f"{path_data}/H2_qubit_hamiltonian.txt", "r") as ham_handle: - string_ham = ham_handle.read() - qubit_operator = string_ham_to_of(string_ham) + qubit_operator = load_operator("mol_H2_qubitham.data", data_directory=path_data, plain_text=True) + with open(f"{path_data}/H2_UCCSD.qasm", "r") as circ_handle: openqasm_circ = circ_handle.read() abs_circ = translator._translate_openqasm2abs(openqasm_circ) diff --git a/tangelo/toolboxes/ansatz_generator/tests/data/mol_H4_doublecation_minao_qubitham_bk.data b/tangelo/toolboxes/ansatz_generator/tests/data/mol_H4_doublecation_minao_qubitham_bk.data new file mode 100644 index 000000000..579a8351b --- /dev/null +++ b/tangelo/toolboxes/ansatz_generator/tests/data/mol_H4_doublecation_minao_qubitham_bk.data @@ -0,0 +1,186 @@ +QubitOperator: +(-0.8450880849596107+0j) [] + +(0.009461086978084402+0j) [X0 X1 X2] + +(0.0020453160866976123+0j) [X0 X1 X2 Z3] + +(0.010646150610004372+0j) [X0 X1 Z3 X4] + +(0.024058656071791626+0j) [X0 X1 Z3 Y4 Y5 Z6] + +(-0.012634174981636713+0j) [X0 X1 Z3 Z4 Y5 Y6] + +(-0.008407495254282343+0j) [X0 X1 Z3 X6] + +(0.011424481090154908+0j) [X0 Y1 X2 Y4 Y5 Y6] + +(-0.0020776113200211103+0j) [X0 Y1 Y2] + +(0.024395190370696842+0j) [X0 Y1 Y2 Z3 Z4 X5 Z7] + +(0.013474635539233035+0j) [X0 Y1 Y2 Z3 Z5 Z6 Z7] + +(-0.01176101538906013+0j) [X0 Y1 Y2 X4 Y5 Y6] + +(0.0003365342989052199+0j) [X0 Y1 Y2 Y4 Y5 X6] + +(0.002844217831601519+0j) [X0 Y1 Y2 Z4] + +(-0.007801932778402854+0j) [X0 Y1 Y2 Z4 Z5] + +(-0.024395190370696842+0j) [X0 Y1 Y2 X5 Z6] + +(0.005067140284950691+0j) [X0 Y1 Y2 Z6] + +(-0.024058656071791626+0j) [X0 Y1 Z2 Z3 Y4 X5 Z7] + +(-0.008407495254282343+0j) [X0 Y1 Z2 Z3 Z5 Y6 Z7] + +(0.010646150610004372+0j) [X0 Y1 Z2 Y4 Z5] + +(0.012634174981636713+0j) [X0 Y1 Z2 X5 Y6] + +(0.026604454109080324+0j) [X0 Z1 X2] + +(0.026604454109080324+0j) [X0 Z1 X2 Z3] + +(0.024934767638066756+0j) [X0 Z1 X4] + +(-0.006238164536781787+0j) [X0 Z1 Y4 Y5 Z6] + +(0.006238164536781785+0j) [X0 Z1 Z4 Y5 Y6] + +(0.017676042848045994+0j) [X0 Z1 X6] + +(0.006238164536781787+0j) [X0 Z3 X4 X5 Z7] + +(0.017676042848045994+0j) [X0 Z3 Z5 X6 Z7] + +(0.024934767638066756+0j) [X0 X4 Z5] + +(-0.006238164536781785+0j) [X0 X5 X6] + +(0.009461086978084402+0j) [Y0 X1 Y2] + +(0.0020453160866976123+0j) [Y0 X1 Y2 Z3] + +(-0.024058656071791626+0j) [Y0 X1 Z3 X4 Y5 Z6] + +(0.010646150610004372+0j) [Y0 X1 Z3 Y4] + +(0.012634174981636713+0j) [Y0 X1 Z3 Z4 Y5 X6] + +(-0.008407495254282343+0j) [Y0 X1 Z3 Y6] + +(0.0020776113200211103+0j) [Y0 Y1 X2] + +(-0.024395190370696842+0j) [Y0 Y1 X2 Z3 Z4 X5 Z7] + +(-0.013474635539233035+0j) [Y0 Y1 X2 Z3 Z5 Z6 Z7] + +(0.0003365342989052199+0j) [Y0 Y1 X2 X4 Y5 Y6] + +(-0.01176101538906013+0j) [Y0 Y1 X2 Y4 Y5 X6] + +(-0.002844217831601519+0j) [Y0 Y1 X2 Z4] + +(0.007801932778402854+0j) [Y0 Y1 X2 Z4 Z5] + +(0.024395190370696842+0j) [Y0 Y1 X2 X5 Z6] + +(-0.005067140284950691+0j) [Y0 Y1 X2 Z6] + +(0.011424481090154908+0j) [Y0 Y1 Y2 X4 Y5 X6] + +(0.024058656071791626+0j) [Y0 Y1 Z2 Z3 X4 X5 Z7] + +(0.008407495254282343+0j) [Y0 Y1 Z2 Z3 Z5 X6 Z7] + +(-0.010646150610004372+0j) [Y0 Y1 Z2 X4 Z5] + +(-0.012634174981636713+0j) [Y0 Y1 Z2 X5 X6] + +(0.026604454109080324+0j) [Y0 Z1 Y2] + +(0.026604454109080324+0j) [Y0 Z1 Y2 Z3] + +(0.006238164536781787+0j) [Y0 Z1 X4 Y5 Z6] + +(0.024934767638066756+0j) [Y0 Z1 Y4] + +(-0.006238164536781785+0j) [Y0 Z1 Z4 Y5 X6] + +(0.017676042848045994+0j) [Y0 Z1 Y6] + +(0.006238164536781787+0j) [Y0 Z3 Y4 X5 Z7] + +(0.017676042848045994+0j) [Y0 Z3 Z5 Y6 Z7] + +(0.024934767638066756+0j) [Y0 Y4 Z5] + +(-0.006238164536781785+0j) [Y0 X5 Y6] + +(0.2347791241622403+0j) [Z0] + +(0.009461086978084402+0j) [Z0 X1 Z2] + +(0.0020453160866976123+0j) [Z0 X1 Z2 Z3] + +(0.002077611320021108+0j) [Z0 X1 Z3] + +(0.024395190370696842+0j) [Z0 X1 Z3 X4 Y5 Y6] + +(-0.024395190370696842+0j) [Z0 X1 Z3 Y4 Y5 X6] + +(0.007801932778402854+0j) [Z0 X1 Z3 Z4] + +(-0.002844217831601519+0j) [Z0 X1 Z3 Z4 Z5] + +(0.0003365342989052199+0j) [Z0 X1 Z3 X5 Z6] + +(-0.013474635539233035+0j) [Z0 X1 Z3 Z6] + +(-0.01176101538906013+0j) [Z0 X1 Z4 X5 Z7] + +(-0.005067140284950691+0j) [Z0 X1 Z5 Z6 Z7] + +(0.012634174981636713+0j) [Z0 Y1 X2 Z3 Y4 X5 Z7] + +(0.008407495254282343+0j) [Z0 Y1 X2 Z3 Z5 Y6 Z7] + +(-0.010646150610004373+0j) [Z0 Y1 X2 Y4 Z5] + +(-0.024058656071791626+0j) [Z0 Y1 X2 X5 Y6] + +(-0.012634174981636713+0j) [Z0 Y1 Y2 Z3 X4 X5 Z7] + +(-0.008407495254282343+0j) [Z0 Y1 Y2 Z3 Z5 X6 Z7] + +(0.010646150610004373+0j) [Z0 Y1 Y2 X4 Z5] + +(0.024058656071791626+0j) [Z0 Y1 Y2 X5 X6] + +(-0.011424481090154908+0j) [Z0 Y1 Z2 Z3 Y5 Z7] + +(0.23477912416224028+0j) [Z0 Z1] + +(0.10416313162196868+0j) [Z0 Z1 Z2] + +(0.10416313162196868+0j) [Z0 Z1 Z2 Z3] + +(0.002583741731301936+0j) [Z0 Z1 Z3 Z4 X5 Z7] + +(0.09333330628587887+0j) [Z0 Z1 Z3 Z5 Z6 Z7] + +(-0.008821906268083723+0j) [Z0 Z1 X4 Y5 Y6] + +(0.008821906268083723+0j) [Z0 Z1 Y4 Y5 X6] + +(0.1022672441737321+0j) [Z0 Z1 Z4] + +(0.07733247653566534+0j) [Z0 Z1 Z4 Z5] + +(-0.002583741731301936+0j) [Z0 Z1 X5 Z6] + +(0.11100934913392485+0j) [Z0 Z1 Z6] + +(0.07755867751288835+0j) [Z0 Z2] + +(0.07755867751288835+0j) [Z0 Z2 Z3] + +(0.008821906268083723+0j) [Z0 Z3 Z4 X5 Z7] + +(0.11100934913392485+0j) [Z0 Z3 Z5 Z6 Z7] + +(-0.002583741731301936+0j) [Z0 X4 Y5 Y6] + +(0.002583741731301936+0j) [Z0 Y4 Y5 X6] + +(0.07733247653566534+0j) [Z0 Z4] + +(0.1022672441737321+0j) [Z0 Z4 Z5] + +(-0.008821906268083723+0j) [Z0 X5 Z6] + +(0.09333330628587887+0j) [Z0 Z6] + +(-0.0020453160866976123+0j) [X1] + +(-0.010646150610004373+0j) [X1 X2 X4] + +(-0.012634174981636713+0j) [X1 X2 Y4 Y5 Z6] + +(0.024058656071791626+0j) [X1 X2 Z4 Y5 Y6] + +(0.008407495254282343+0j) [X1 X2 X6] + +(0.012634174981636713+0j) [X1 Y2 X4 Y5 Z6] + +(-0.010646150610004373+0j) [X1 Y2 Y4] + +(-0.024058656071791626+0j) [X1 Y2 Z4 Y5 X6] + +(0.008407495254282343+0j) [X1 Y2 Y6] + +(-0.002077611320021108+0j) [X1 Z2] + +(0.0003365342989052199+0j) [X1 Z2 Z3 Z4 X5 Z7] + +(0.005067140284950691+0j) [X1 Z2 Z3 Z5 Z6 Z7] + +(-0.024395190370696842+0j) [X1 Z2 X4 Y5 Y6] + +(0.024395190370696842+0j) [X1 Z2 Y4 Y5 X6] + +(-0.007801932778402854+0j) [X1 Z2 Z4] + +(0.002844217831601519+0j) [X1 Z2 Z4 Z5] + +(-0.01176101538906013+0j) [X1 Z2 X5 Z6] + +(0.013474635539233035+0j) [X1 Z2 Z6] + +(-0.009461086978084402+0j) [X1 Z3] + +(-0.011424481090154908+0j) [Y1 Z3 Z4 Y5 Z6] + +(0.11171627890094807+0j) [Z1] + +(0.019348830306850157+0j) [Z1 X2 Z3 X4] + +(0.008901638567158992+0j) [Z1 X2 Z3 Y4 Y5 Z6] + +(-0.008901638567158992+0j) [Z1 X2 Z3 Z4 Y5 Y6] + +(0.02661467635818076+0j) [Z1 X2 Z3 X6] + +(-0.008901638567158992+0j) [Z1 Y2 Z3 X4 Y5 Z6] + +(0.019348830306850157+0j) [Z1 Y2 Z3 Y4] + +(0.008901638567158992+0j) [Z1 Y2 Z3 Z4 Y5 X6] + +(0.02661467635818076+0j) [Z1 Y2 Z3 Y6] + +(0.0787379260830442+0j) [Z1 Z2 Z3] + +(0.0036633326618679494+0j) [Z1 Z2 Z3 X4 Y5 Y6] + +(-0.0036633326618679494+0j) [Z1 Z2 Z3 Y4 Y5 X6] + +(0.10352319703975528+0j) [Z1 Z2 Z3 Z4] + +(0.08417436673290511+0j) [Z1 Z2 Z3 Z4 Z5] + +(-0.005238305905291042+0j) [Z1 Z2 Z3 X5 Z6] + +(0.10589625078359584+0j) [Z1 Z2 Z3 Z6] + +(0.005238305905291042+0j) [Z1 Z2 Z4 X5 Z7] + +(0.07928157442541509+0j) [Z1 Z2 Z5 Z6 Z7] + +(0.10702518092938992+0j) [Z1 Z3] + +(-0.008901638567158992+0j) [X2 Z3 X4 X5 Z7] + +(0.02661467635818076+0j) [X2 Z3 Z5 X6 Z7] + +(0.019348830306850157+0j) [X2 X4 Z5] + +(0.008901638567158992+0j) [X2 X5 X6] + +(-0.008901638567158992+0j) [Y2 Z3 Y4 X5 Z7] + +(0.02661467635818076+0j) [Y2 Z3 Z5 Y6 Z7] + +(0.019348830306850157+0j) [Y2 Y4 Z5] + +(0.008901638567158992+0j) [Y2 X5 Y6] + +(0.07873792608304406+0j) [Z2] + +(-0.0036633326618679494+0j) [Z2 Z3 Z4 X5 Z7] + +(0.10589625078359584+0j) [Z2 Z3 Z5 Z6 Z7] + +(-0.005238305905291042+0j) [Z2 X4 Y5 Y6] + +(0.005238305905291042+0j) [Z2 Y4 Y5 X6] + +(0.08417436673290511+0j) [Z2 Z4] + +(0.10352319703975528+0j) [Z2 Z4 Z5] + +(0.0036633326618679494+0j) [Z2 X5 Z6] + +(0.07928157442541509+0j) [Z2 Z6] + +(0.012996931212451417+0j) [Z3 X4 X5 X6 Z7] + +(0.02568994957183004+0j) [Z3 X4 Z5 X6 Z7] + +(0.012996931212451417+0j) [Z3 Y4 X5 Y6 Z7] + +(0.02568994957183004+0j) [Z3 Y4 Z5 Y6 Z7] + +(0.012996931212451417+0j) [Z3 Z4 X5 Z6 Z7] + +(0.00448325259770644+0j) [Z3 Z4 X5 Z7] + +(0.103537889856344+0j) [Z3 Z4 Z5 Z6 Z7] + +(0.07784794028451396+0j) [Z3 Z4 Z6 Z7] + +(-0.007649625311503685+0j) [Z3 X5 Z7] + +(-0.12227609382406396+0j) [Z3 Z5 Z6 Z7] + +(0.11970161375543727+0j) [Z3 Z5 Z7] + +(0.007649625311503685+0j) [X4 X5 X6] + +(-0.004483252597706442+0j) [X4 Y5 Y6] + +(0.02568994957183004+0j) [X4 Z5 X6] + +(0.007649625311503685+0j) [Y4 X5 Y6] + +(0.004483252597706442+0j) [Y4 Y5 X6] + +(0.02568994957183004+0j) [Y4 Z5 Y6] + +(0.07666866090982458+0j) [Z4] + +(0.007649625311503685+0j) [Z4 X5 Z6] + +(0.07666866090982456+0j) [Z4 Z5] + +(0.103537889856344+0j) [Z4 Z5 Z6] + +(0.07784794028451396+0j) [Z4 Z6] + +(-0.012996931212451417+0j) [X5] + +(-0.00448325259770644+0j) [X5 Z6] + +(0.10915899681068504+0j) [Z5] + +(-0.12227609382406396+0j) [Z6] \ No newline at end of file diff --git a/tangelo/toolboxes/ansatz_generator/tests/data/mol_H4_doublecation_minao_qubitham_bk_updown.data b/tangelo/toolboxes/ansatz_generator/tests/data/mol_H4_doublecation_minao_qubitham_bk_updown.data new file mode 100644 index 000000000..038b689d5 --- /dev/null +++ b/tangelo/toolboxes/ansatz_generator/tests/data/mol_H4_doublecation_minao_qubitham_bk_updown.data @@ -0,0 +1,186 @@ +QubitOperator: +(-0.845088084959611+0j) [] + +(-0.002077611320021229+0j) [X0] + +(-0.006238164536781798+0j) [X0 X1 Z3 X4 Y5 Y6] + +(0.006238164536781798+0j) [X0 X1 Z3 Y4 Y5 X6] + +(0.017676042848045997+0j) [X0 X1 Z3 Y4 Y5 Z6] + +(0.012634174981636714+0j) [X0 X1 Z3 Z4 Y5 Y6] + +(0.012634174981636714+0j) [X0 X1 Z3 X5 X6] + +(0.008407495254282341+0j) [X0 X1 Z3 X5 Z6] + +(0.017676042848045997+0j) [X0 X1 X4 X5 Z7] + +(-0.008407495254282341+0j) [X0 X1 Z4 X5 Z7] + +(-0.006238164536781799+0j) [X0 Y1 Y2 Z3 X4 X5 Z7] + +(0.024058656071791636+0j) [X0 Y1 Y2 Z3 Z4 X5 Z7] + +(0.024934767638066777+0j) [X0 Y1 Y2 X4 Y5 Y6] + +(-0.024934767638066777+0j) [X0 Y1 Y2 Y4 Y5 X6] + +(-0.006238164536781799+0j) [X0 Y1 Y2 Y4 Y5 Z6] + +(0.010646150610004382+0j) [X0 Y1 Y2 Z4 Y5 Y6] + +(0.010646150610004382+0j) [X0 Y1 Y2 X5 X6] + +(-0.024058656071791636+0j) [X0 Y1 Y2 X5 Z6] + +(0.002077611320021229+0j) [X0 Z1] + +(0.0003365342989052225+0j) [X0 Z1 X2] + +(0.0003365342989052225+0j) [X0 Z1 X2 Z3] + +(-0.0028442178316015095+0j) [X0 Z1 Z2] + +(0.005067140284950678+0j) [X0 Z1 Z2 Z3] + +(-0.024395190370696863+0j) [X0 Z1 Z3 Z5 X6 Z7] + +(-0.013474635539233018+0j) [X0 Z1 Z3 Z5 Z6 Z7] + +(-0.026604454109080324+0j) [X0 Z1 X4] + +(0.026604454109080324+0j) [X0 Z1 X4 Z5] + +(-0.009461086978084409+0j) [X0 Z1 Z4] + +(0.0020453160866976366+0j) [X0 Z1 Z4 Z5] + +(0.024395190370696863+0j) [X0 Z1 X6] + +(0.007801932778402873+0j) [X0 Z1 Z6] + +(-0.011761015389060145+0j) [X0 X2] + +(-0.011761015389060145+0j) [X0 X2 Z3] + +(0.0028442178316015095+0j) [X0 Z2] + +(-0.005067140284950678+0j) [X0 Z2 Z3] + +(0.024395190370696863+0j) [X0 Z3 Z5 X6 Z7] + +(0.013474635539233018+0j) [X0 Z3 Z5 Z6 Z7] + +(0.026604454109080324+0j) [X0 X4] + +(-0.026604454109080324+0j) [X0 X4 Z5] + +(0.009461086978084409+0j) [X0 Z4] + +(-0.0020453160866976366+0j) [X0 Z4 Z5] + +(-0.024395190370696863+0j) [X0 X6] + +(-0.007801932778402873+0j) [X0 Z6] + +(0.006238164536781799+0j) [Y0 Y1 X2 Z3 X4 X5 Z7] + +(-0.024058656071791636+0j) [Y0 Y1 X2 Z3 Z4 X5 Z7] + +(-0.024934767638066777+0j) [Y0 Y1 X2 X4 Y5 Y6] + +(0.024934767638066777+0j) [Y0 Y1 X2 Y4 Y5 X6] + +(0.006238164536781799+0j) [Y0 Y1 X2 Y4 Y5 Z6] + +(-0.010646150610004382+0j) [Y0 Y1 X2 Z4 Y5 Y6] + +(-0.010646150610004382+0j) [Y0 Y1 X2 X5 X6] + +(0.024058656071791636+0j) [Y0 Y1 X2 X5 Z6] + +(0.017676042848045997+0j) [Y0 Y1 Z2 Z3 X4 X5 Z7] + +(-0.008407495254282341+0j) [Y0 Y1 Z2 Z3 Z4 X5 Z7] + +(-0.006238164536781798+0j) [Y0 Y1 Z2 X4 Y5 Y6] + +(0.006238164536781798+0j) [Y0 Y1 Z2 Y4 Y5 X6] + +(0.017676042848045997+0j) [Y0 Y1 Z2 Y4 Y5 Z6] + +(0.012634174981636714+0j) [Y0 Y1 Z2 Z4 Y5 Y6] + +(0.012634174981636714+0j) [Y0 Y1 Z2 X5 X6] + +(0.008407495254282341+0j) [Y0 Y1 Z2 X5 Z6] + +(-0.011424481090154922+0j) [Y0 Z1 Y2] + +(-0.011424481090154922+0j) [Y0 Z1 Y2 Z3] + +(0.23477912416224012+0j) [Z0] + +(0.024058656071791636+0j) [Z0 X1 Z3 X4 Y5 Y6] + +(-0.024058656071791636+0j) [Z0 X1 Z3 Y4 Y5 X6] + +(-0.008407495254282341+0j) [Z0 X1 Z3 Y4 Y5 Z6] + +(0.008901638567159007+0j) [Z0 X1 Z3 Z4 Y5 Y6] + +(0.008901638567159007+0j) [Z0 X1 Z3 X5 X6] + +(-0.026614676358180763+0j) [Z0 X1 Z3 X5 Z6] + +(-0.008407495254282341+0j) [Z0 X1 X4 X5 Z7] + +(0.026614676358180763+0j) [Z0 X1 Z4 X5 Z7] + +(0.012634174981636714+0j) [Z0 Y1 Y2 Z3 X4 X5 Z7] + +(0.008901638567159005+0j) [Z0 Y1 Y2 Z3 Z4 X5 Z7] + +(0.01064615061000438+0j) [Z0 Y1 Y2 X4 Y5 Y6] + +(-0.01064615061000438+0j) [Z0 Y1 Y2 Y4 Y5 X6] + +(0.012634174981636714+0j) [Z0 Y1 Y2 Y4 Y5 Z6] + +(0.01934883030685017+0j) [Z0 Y1 Y2 Z4 Y5 Y6] + +(0.01934883030685017+0j) [Z0 Y1 Y2 X5 X6] + +(-0.008901638567159005+0j) [Z0 Y1 Y2 X5 Z6] + +(0.07873792608304385+0j) [Z0 Z1] + +(-0.005238305905291046+0j) [Z0 Z1 X2] + +(0.0025837417313019457+0j) [Z0 Z1 X2 Z3] + +(0.0841743667329052+0j) [Z0 Z1 Z2] + +(0.0933333062858789+0j) [Z0 Z1 Z2 Z3] + +(-0.0036633326618679607+0j) [Z0 Z1 Z3 Z5 X6 Z7] + +(0.10589625078359585+0j) [Z0 Z1 Z3 Z5 Z6 Z7] + +(-0.0020453160866976366+0j) [Z0 Z1 X4] + +(0.0020453160866976366+0j) [Z0 Z1 X4 Z5] + +(0.1041631316219687+0j) [Z0 Z1 Z4] + +(0.10702518092938995+0j) [Z0 Z1 Z4 Z5] + +(0.0036633326618679607+0j) [Z0 Z1 X6] + +(0.10352319703975536+0j) [Z0 Z1 Z6] + +(-0.0025837417313019457+0j) [Z0 X2] + +(0.005238305905291046+0j) [Z0 X2 Z3] + +(0.07733247653566547+0j) [Z0 Z2] + +(0.07928157442541509+0j) [Z0 Z2 Z3] + +(0.008821906268083745+0j) [Z0 Z3 Z5 X6 Z7] + +(0.11100934913392489+0j) [Z0 Z3 Z5 Z6 Z7] + +(0.009461086978084409+0j) [Z0 X4] + +(-0.009461086978084409+0j) [Z0 X4 Z5] + +(0.11171627890094815+0j) [Z0 Z4] + +(0.1041631316219687+0j) [Z0 Z4 Z5] + +(-0.008821906268083745+0j) [Z0 X6] + +(0.10226724417373226+0j) [Z0 Z6] + +(0.012634174981636714+0j) [X1 X2 Z3 X4 X5 Z7] + +(0.008901638567159005+0j) [X1 X2 Z3 Z4 X5 Z7] + +(0.01064615061000438+0j) [X1 X2 X4 Y5 Y6] + +(-0.01064615061000438+0j) [X1 X2 Y4 Y5 X6] + +(0.012634174981636714+0j) [X1 X2 Y4 Y5 Z6] + +(0.01934883030685017+0j) [X1 X2 Z4 Y5 Y6] + +(0.01934883030685017+0j) [X1 X2 X5 X6] + +(-0.008901638567159005+0j) [X1 X2 X5 Z6] + +(0.008407495254282341+0j) [X1 Z2 Z3 X4 X5 Z7] + +(-0.026614676358180763+0j) [X1 Z2 Z3 Z4 X5 Z7] + +(-0.024058656071791636+0j) [X1 Z2 X4 Y5 Y6] + +(0.024058656071791636+0j) [X1 Z2 Y4 Y5 X6] + +(0.008407495254282341+0j) [X1 Z2 Y4 Y5 Z6] + +(-0.008901638567159007+0j) [X1 Z2 Z4 Y5 Y6] + +(-0.008901638567159007+0j) [X1 Z2 X5 X6] + +(0.026614676358180763+0j) [X1 Z2 X5 Z6] + +(0.07755867751288838+0j) [Z1] + +(0.0044832525977064434+0j) [Z1 X2 Z3] + +(0.024395190370696863+0j) [Z1 X2 Z3 X4] + +(-0.024395190370696863+0j) [Z1 X2 Z3 X4 Z5] + +(0.008821906268083745+0j) [Z1 X2 Z3 Z4] + +(-0.0036633326618679607+0j) [Z1 X2 Z3 Z4 Z5] + +(-0.025689949571830066+0j) [Z1 X2 Z3 X6] + +(-0.007649625311503682+0j) [Z1 X2 Z3 Z6] + +(0.025689949571830066+0j) [Z1 X2 Z5 X6 Z7] + +(0.01299693121245138+0j) [Z1 X2 Z5 Z6 Z7] + +(-0.1222760938240641+0j) [Z1 Z2 Z3] + +(0.013474635539233018+0j) [Z1 Z2 Z3 X4] + +(-0.013474635539233018+0j) [Z1 Z2 Z3 X4 Z5] + +(0.11100934913392489+0j) [Z1 Z2 Z3 Z4] + +(0.10589625078359585+0j) [Z1 Z2 Z3 Z4 Z5] + +(-0.01299693121245138+0j) [Z1 Z2 Z3 X6] + +(0.10353788985634407+0j) [Z1 Z2 Z3 Z6] + +(0.01299693121245138+0j) [Z1 Z2 Z5 X6 Z7] + +(0.11970161375543717+0j) [Z1 Z2 Z5 Z6 Z7] + +(0.077847940284514+0j) [Z1 Z3] + +(-0.0044832525977064434+0j) [X2] + +(-0.025689949571830066+0j) [X2 Z3 Z5 X6 Z7] + +(-0.01299693121245138+0j) [X2 Z3 Z5 Z6 Z7] + +(-0.024395190370696863+0j) [X2 X4] + +(0.024395190370696863+0j) [X2 X4 Z5] + +(-0.008821906268083745+0j) [X2 Z4] + +(0.0036633326618679607+0j) [X2 Z4 Z5] + +(0.025689949571830066+0j) [X2 X6] + +(0.007649625311503682+0j) [X2 Z6] + +(0.07666866090982474+0j) [Z2] + +(-0.007649625311503682+0j) [Z2 Z3 Z5 X6 Z7] + +(0.10353788985634407+0j) [Z2 Z3 Z5 Z6 Z7] + +(-0.007801932778402873+0j) [Z2 X4] + +(0.007801932778402873+0j) [Z2 X4 Z5] + +(0.10226724417373226+0j) [Z2 Z4] + +(0.10352319703975536+0j) [Z2 Z4 Z5] + +(0.007649625311503682+0j) [Z2 X6] + +(0.10915899681068522+0j) [Z2 Z6] + +(0.0003365342989052225+0j) [Z3 X4 Z5 X6 Z7] + +(0.005067140284950678+0j) [Z3 X4 Z5 Z6 Z7] + +(-0.011761015389060145+0j) [Z3 X4 X6 Z7] + +(-0.005067140284950678+0j) [Z3 X4 Z6 Z7] + +(-0.011424481090154922+0j) [Z3 Y4 Z5 Y6 Z7] + +(0.0025837417313019457+0j) [Z3 Z4 Z5 X6 Z7] + +(0.0933333062858789+0j) [Z3 Z4 Z5 Z6 Z7] + +(0.005238305905291046+0j) [Z3 Z4 X6 Z7] + +(0.07928157442541509+0j) [Z3 Z4 Z6 Z7] + +(0.004483252597706446+0j) [Z3 Z5 X6 Z7] + +(-0.12227609382406399+0j) [Z3 Z5 Z6 Z7] + +(0.077847940284514+0j) [Z3 Z5 Z7] + +(-0.002077611320021231+0j) [X4] + +(0.002077611320021231+0j) [X4 Z5] + +(0.0003365342989052225+0j) [X4 Z5 X6] + +(-0.0028442178316015095+0j) [X4 Z5 Z6] + +(-0.011761015389060145+0j) [X4 X6] + +(0.0028442178316015095+0j) [X4 Z6] + +(-0.011424481090154922+0j) [Y4 Z5 Y6] + +(0.23477912416224034+0j) [Z4] + +(0.07873792608304381+0j) [Z4 Z5] + +(-0.005238305905291046+0j) [Z4 Z5 X6] + +(0.0841743667329052+0j) [Z4 Z5 Z6] + +(-0.0025837417313019457+0j) [Z4 X6] + +(0.07733247653566547+0j) [Z4 Z6] + +(0.07755867751288838+0j) [Z5] + +(-0.004483252597706446+0j) [X6] + +(0.0766686609098248+0j) [Z6] \ No newline at end of file diff --git a/tangelo/toolboxes/ansatz_generator/tests/data/mol_H4_doublecation_minao_qubitham_jw.data b/tangelo/toolboxes/ansatz_generator/tests/data/mol_H4_doublecation_minao_qubitham_jw.data new file mode 100644 index 000000000..408bc2144 --- /dev/null +++ b/tangelo/toolboxes/ansatz_generator/tests/data/mol_H4_doublecation_minao_qubitham_jw.data @@ -0,0 +1,186 @@ +QubitOperator: +(-0.845088084959611+0j) [] + +(-0.026604454109080324+0j) [X0 X1 Y2 Y3] + +(-0.024934767638066777+0j) [X0 X1 Y4 Y5] + +(-0.006238164536781799+0j) [X0 X1 Y4 Z5 Z6 Y7] + +(-0.006238164536781798+0j) [X0 X1 X5 X6] + +(-0.017676042848045997+0j) [X0 X1 Y6 Y7] + +(0.026604454109080324+0j) [X0 Y1 Y2 X3] + +(0.024934767638066777+0j) [X0 Y1 Y4 X5] + +(0.006238164536781799+0j) [X0 Y1 Y4 Z5 Z6 X7] + +(-0.006238164536781798+0j) [X0 Y1 Y5 X6] + +(0.017676042848045997+0j) [X0 Y1 Y6 X7] + +(-0.002077611320021229+0j) [X0 Z1 X2] + +(-0.0020453160866976366+0j) [X0 Z1 X2 Z3] + +(-0.011761015389060145+0j) [X0 Z1 X2 X4 Z5 X6] + +(-0.0003365342989052225+0j) [X0 Z1 X2 Y4 Z5 Y6] + +(0.0028442178316015095+0j) [X0 Z1 X2 Z4] + +(-0.024395190370696863+0j) [X0 Z1 X2 X5 Z6 X7] + +(-0.024395190370696863+0j) [X0 Z1 X2 Y5 Z6 Y7] + +(-0.007801932778402873+0j) [X0 Z1 X2 Z5] + +(0.005067140284950678+0j) [X0 Z1 X2 Z6] + +(0.013474635539233018+0j) [X0 Z1 X2 Z7] + +(-0.011424481090154922+0j) [X0 Z1 Y2 Y4 Z5 X6] + +(-0.010646150610004382+0j) [X0 Z1 Z2 X3 Y4 Y5] + +(0.024058656071791636+0j) [X0 Z1 Z2 X3 Y4 Z5 Z6 Y7] + +(0.012634174981636714+0j) [X0 Z1 Z2 X3 X5 X6] + +(0.008407495254282341+0j) [X0 Z1 Z2 X3 Y6 Y7] + +(0.010646150610004382+0j) [X0 Z1 Z2 Y3 Y4 X5] + +(-0.024058656071791636+0j) [X0 Z1 Z2 Y3 Y4 Z5 Z6 X7] + +(0.012634174981636714+0j) [X0 Z1 Z2 Y3 Y5 X6] + +(-0.008407495254282341+0j) [X0 Z1 Z2 Y3 Y6 X7] + +(0.009461086978084409+0j) [X0 X2] + +(0.026604454109080324+0j) [Y0 X1 X2 Y3] + +(0.024934767638066777+0j) [Y0 X1 X4 Y5] + +(0.006238164536781799+0j) [Y0 X1 X4 Z5 Z6 Y7] + +(-0.006238164536781798+0j) [Y0 X1 X5 Y6] + +(0.017676042848045997+0j) [Y0 X1 X6 Y7] + +(-0.026604454109080324+0j) [Y0 Y1 X2 X3] + +(-0.024934767638066777+0j) [Y0 Y1 X4 X5] + +(-0.006238164536781799+0j) [Y0 Y1 X4 Z5 Z6 X7] + +(-0.006238164536781798+0j) [Y0 Y1 Y5 Y6] + +(-0.017676042848045997+0j) [Y0 Y1 X6 X7] + +(-0.011424481090154922+0j) [Y0 Z1 X2 X4 Z5 Y6] + +(-0.002077611320021229+0j) [Y0 Z1 Y2] + +(-0.0020453160866976366+0j) [Y0 Z1 Y2 Z3] + +(-0.0003365342989052225+0j) [Y0 Z1 Y2 X4 Z5 X6] + +(-0.011761015389060145+0j) [Y0 Z1 Y2 Y4 Z5 Y6] + +(0.0028442178316015095+0j) [Y0 Z1 Y2 Z4] + +(-0.024395190370696863+0j) [Y0 Z1 Y2 X5 Z6 X7] + +(-0.024395190370696863+0j) [Y0 Z1 Y2 Y5 Z6 Y7] + +(-0.007801932778402873+0j) [Y0 Z1 Y2 Z5] + +(0.005067140284950678+0j) [Y0 Z1 Y2 Z6] + +(0.013474635539233018+0j) [Y0 Z1 Y2 Z7] + +(0.010646150610004382+0j) [Y0 Z1 Z2 X3 X4 Y5] + +(-0.024058656071791636+0j) [Y0 Z1 Z2 X3 X4 Z5 Z6 Y7] + +(0.012634174981636714+0j) [Y0 Z1 Z2 X3 X5 Y6] + +(-0.008407495254282341+0j) [Y0 Z1 Z2 X3 X6 Y7] + +(-0.010646150610004382+0j) [Y0 Z1 Z2 Y3 X4 X5] + +(0.024058656071791636+0j) [Y0 Z1 Z2 Y3 X4 Z5 Z6 X7] + +(0.012634174981636714+0j) [Y0 Z1 Z2 Y3 Y5 Y6] + +(0.008407495254282341+0j) [Y0 Z1 Z2 Y3 X6 X7] + +(0.009461086978084409+0j) [Y0 Y2] + +(0.23477912416224012+0j) [Z0] + +(0.009461086978084409+0j) [Z0 X1 Z2 X3] + +(0.009461086978084409+0j) [Z0 Y1 Z2 Y3] + +(0.11171627890094815+0j) [Z0 Z1] + +(0.07755867751288838+0j) [Z0 Z2] + +(0.1041631316219687+0j) [Z0 Z3] + +(-0.0025837417313019457+0j) [Z0 X4 Z5 X6] + +(-0.0025837417313019457+0j) [Z0 Y4 Z5 Y6] + +(0.07733247653566547+0j) [Z0 Z4] + +(-0.008821906268083745+0j) [Z0 X5 Z6 X7] + +(-0.008821906268083745+0j) [Z0 Y5 Z6 Y7] + +(0.10226724417373226+0j) [Z0 Z5] + +(0.0933333062858789+0j) [Z0 Z6] + +(0.11100934913392489+0j) [Z0 Z7] + +(-0.01064615061000438+0j) [X1 X2 X4 X5] + +(0.012634174981636714+0j) [X1 X2 X4 Z5 Z6 X7] + +(0.024058656071791636+0j) [X1 X2 Y5 Y6] + +(0.008407495254282341+0j) [X1 X2 X6 X7] + +(-0.01064615061000438+0j) [X1 Y2 Y4 X5] + +(0.012634174981636714+0j) [X1 Y2 Y4 Z5 Z6 X7] + +(-0.024058656071791636+0j) [X1 Y2 Y5 X6] + +(0.008407495254282341+0j) [X1 Y2 Y6 X7] + +(-0.002077611320021231+0j) [X1 Z2 X3] + +(-0.024395190370696863+0j) [X1 Z2 X3 X4 Z5 X6] + +(-0.024395190370696863+0j) [X1 Z2 X3 Y4 Z5 Y6] + +(-0.007801932778402873+0j) [X1 Z2 X3 Z4] + +(-0.011761015389060145+0j) [X1 Z2 X3 X5 Z6 X7] + +(-0.0003365342989052225+0j) [X1 Z2 X3 Y5 Z6 Y7] + +(0.0028442178316015095+0j) [X1 Z2 X3 Z5] + +(0.013474635539233018+0j) [X1 Z2 X3 Z6] + +(0.005067140284950678+0j) [X1 Z2 X3 Z7] + +(-0.011424481090154922+0j) [X1 Z2 Y3 Y5 Z6 X7] + +(-0.0020453160866976366+0j) [X1 X3] + +(-0.01064615061000438+0j) [Y1 X2 X4 Y5] + +(0.012634174981636714+0j) [Y1 X2 X4 Z5 Z6 Y7] + +(-0.024058656071791636+0j) [Y1 X2 X5 Y6] + +(0.008407495254282341+0j) [Y1 X2 X6 Y7] + +(-0.01064615061000438+0j) [Y1 Y2 Y4 Y5] + +(0.012634174981636714+0j) [Y1 Y2 Y4 Z5 Z6 Y7] + +(0.024058656071791636+0j) [Y1 Y2 X5 X6] + +(0.008407495254282341+0j) [Y1 Y2 Y6 Y7] + +(-0.011424481090154922+0j) [Y1 Z2 X3 X5 Z6 Y7] + +(-0.002077611320021231+0j) [Y1 Z2 Y3] + +(-0.024395190370696863+0j) [Y1 Z2 Y3 X4 Z5 X6] + +(-0.024395190370696863+0j) [Y1 Z2 Y3 Y4 Z5 Y6] + +(-0.007801932778402873+0j) [Y1 Z2 Y3 Z4] + +(-0.0003365342989052225+0j) [Y1 Z2 Y3 X5 Z6 X7] + +(-0.011761015389060145+0j) [Y1 Z2 Y3 Y5 Z6 Y7] + +(0.0028442178316015095+0j) [Y1 Z2 Y3 Z5] + +(0.013474635539233018+0j) [Y1 Z2 Y3 Z6] + +(0.005067140284950678+0j) [Y1 Z2 Y3 Z7] + +(-0.0020453160866976366+0j) [Y1 Y3] + +(0.23477912416224034+0j) [Z1] + +(0.1041631316219687+0j) [Z1 Z2] + +(0.07755867751288838+0j) [Z1 Z3] + +(-0.008821906268083745+0j) [Z1 X4 Z5 X6] + +(-0.008821906268083745+0j) [Z1 Y4 Z5 Y6] + +(0.10226724417373226+0j) [Z1 Z4] + +(-0.0025837417313019457+0j) [Z1 X5 Z6 X7] + +(-0.0025837417313019457+0j) [Z1 Y5 Z6 Y7] + +(0.07733247653566547+0j) [Z1 Z5] + +(0.11100934913392489+0j) [Z1 Z6] + +(0.0933333062858789+0j) [Z1 Z7] + +(-0.01934883030685017+0j) [X2 X3 Y4 Y5] + +(0.008901638567159005+0j) [X2 X3 Y4 Z5 Z6 Y7] + +(0.008901638567159007+0j) [X2 X3 X5 X6] + +(-0.026614676358180763+0j) [X2 X3 Y6 Y7] + +(0.01934883030685017+0j) [X2 Y3 Y4 X5] + +(-0.008901638567159005+0j) [X2 Y3 Y4 Z5 Z6 X7] + +(0.008901638567159007+0j) [X2 Y3 Y5 X6] + +(0.026614676358180763+0j) [X2 Y3 Y6 X7] + +(0.01934883030685017+0j) [Y2 X3 X4 Y5] + +(-0.008901638567159005+0j) [Y2 X3 X4 Z5 Z6 Y7] + +(0.008901638567159007+0j) [Y2 X3 X5 Y6] + +(0.026614676358180763+0j) [Y2 X3 X6 Y7] + +(-0.01934883030685017+0j) [Y2 Y3 X4 X5] + +(0.008901638567159005+0j) [Y2 Y3 X4 Z5 Z6 X7] + +(0.008901638567159007+0j) [Y2 Y3 Y5 Y6] + +(-0.026614676358180763+0j) [Y2 Y3 X6 X7] + +(0.07873792608304385+0j) [Z2] + +(0.10702518092938995+0j) [Z2 Z3] + +(-0.005238305905291046+0j) [Z2 X4 Z5 X6] + +(-0.005238305905291046+0j) [Z2 Y4 Z5 Y6] + +(0.0841743667329052+0j) [Z2 Z4] + +(0.0036633326618679607+0j) [Z2 X5 Z6 X7] + +(0.0036633326618679607+0j) [Z2 Y5 Z6 Y7] + +(0.10352319703975536+0j) [Z2 Z5] + +(0.07928157442541509+0j) [Z2 Z6] + +(0.10589625078359585+0j) [Z2 Z7] + +(0.07873792608304381+0j) [Z3] + +(0.0036633326618679607+0j) [Z3 X4 Z5 X6] + +(0.0036633326618679607+0j) [Z3 Y4 Z5 Y6] + +(0.10352319703975536+0j) [Z3 Z4] + +(-0.005238305905291046+0j) [Z3 X5 Z6 X7] + +(-0.005238305905291046+0j) [Z3 Y5 Z6 Y7] + +(0.0841743667329052+0j) [Z3 Z5] + +(0.10589625078359585+0j) [Z3 Z6] + +(0.07928157442541509+0j) [Z3 Z7] + +(-0.025689949571830066+0j) [X4 X5 Y6 Y7] + +(0.025689949571830066+0j) [X4 Y5 Y6 X7] + +(-0.0044832525977064434+0j) [X4 Z5 X6] + +(-0.01299693121245138+0j) [X4 Z5 X6 Z7] + +(0.007649625311503682+0j) [X4 X6] + +(0.025689949571830066+0j) [Y4 X5 X6 Y7] + +(-0.025689949571830066+0j) [Y4 Y5 X6 X7] + +(-0.0044832525977064434+0j) [Y4 Z5 Y6] + +(-0.01299693121245138+0j) [Y4 Z5 Y6 Z7] + +(0.007649625311503682+0j) [Y4 Y6] + +(0.07666866090982474+0j) [Z4] + +(0.007649625311503682+0j) [Z4 X5 Z6 X7] + +(0.007649625311503682+0j) [Z4 Y5 Z6 Y7] + +(0.10915899681068522+0j) [Z4 Z5] + +(0.077847940284514+0j) [Z4 Z6] + +(0.10353788985634407+0j) [Z4 Z7] + +(-0.004483252597706446+0j) [X5 Z6 X7] + +(-0.01299693121245138+0j) [X5 X7] + +(-0.004483252597706446+0j) [Y5 Z6 Y7] + +(-0.01299693121245138+0j) [Y5 Y7] + +(0.0766686609098248+0j) [Z5] + +(0.10353788985634407+0j) [Z5 Z6] + +(0.077847940284514+0j) [Z5 Z7] + +(-0.1222760938240641+0j) [Z6] + +(0.11970161375543717+0j) [Z6 Z7] + +(-0.12227609382406399+0j) [Z7] \ No newline at end of file diff --git a/tangelo/toolboxes/ansatz_generator/tests/test_hea.py b/tangelo/toolboxes/ansatz_generator/tests/test_hea.py index 306dee17b..1b84a5950 100644 --- a/tangelo/toolboxes/ansatz_generator/tests/test_hea.py +++ b/tangelo/toolboxes/ansatz_generator/tests/test_hea.py @@ -14,6 +14,8 @@ import unittest import numpy as np +import os +from openfermion import load_operator from tangelo.molecule_library import mol_H2_sto3g, mol_H4_doublecation_minao from tangelo.toolboxes.qubit_mappings import jordan_wigner @@ -24,6 +26,9 @@ # Initiate simulator sim = Simulator() +# For openfermion.load_operator function. +pwd_this_test = os.path.dirname(os.path.abspath(__file__)) + class HEATest(unittest.TestCase): @@ -92,7 +97,7 @@ def test_hea_H4_doublecation(self): hea_ansatz.build_circuit() # Build qubit hamiltonian for energy evaluation - qubit_hamiltonian = jordan_wigner(mol_H4_doublecation_minao.fermionic_hamiltonian) + qubit_hamiltonian = load_operator("mol_H4_doublecation_minao_qubitham_jw.data", data_directory=pwd_this_test+"/data", plain_text=True) # Assert energy returned is as expected for given parameters hea_ansatz.update_var_params(var_params) diff --git a/tangelo/toolboxes/ansatz_generator/tests/test_qcc.py b/tangelo/toolboxes/ansatz_generator/tests/test_qcc.py index e52920eed..8d392aea2 100644 --- a/tangelo/toolboxes/ansatz_generator/tests/test_qcc.py +++ b/tangelo/toolboxes/ansatz_generator/tests/test_qcc.py @@ -16,6 +16,8 @@ import unittest import numpy as np +import os +from openfermion import load_operator from tangelo.linq import Simulator from tangelo.toolboxes.ansatz_generator.qmf import QMF @@ -25,6 +27,9 @@ sim = Simulator() +# For openfermion.load_operator function. +pwd_this_test = os.path.dirname(os.path.abspath(__file__)) + class QCCTest(unittest.TestCase): """Unit tests for various functionalities of the QCC ansatz class. Examples for both closed- @@ -36,7 +41,7 @@ class QCCTest(unittest.TestCase): def test_qcc_set_var_params(): """ Verify behavior of set_var_params for different inputs (keyword, list, numpy array). """ - qcc_ansatz = QCC(mol_H2_sto3g) + qcc_ansatz = QCC(mol_H2_sto3g, up_then_down=True) one_zero = np.zeros((1,), dtype=float) @@ -57,7 +62,7 @@ def test_qcc_set_var_params(): def test_qcc_incorrect_number_var_params(self): """ Return an error if user provide incorrect number of variational parameters """ - qcc_ansatz = QCC(mol_H2_sto3g) + qcc_ansatz = QCC(mol_H2_sto3g, up_then_down=True) self.assertRaises(ValueError, qcc_ansatz.set_var_params, np.array([1.] * 2)) @@ -173,7 +178,7 @@ def test_qcc_h4_double_cation(self): qcc_ansatz.build_circuit() # Get qubit hamiltonian for energy evaluation - qubit_hamiltonian = qcc_ansatz.qubit_ham + qubit_hamiltonian = load_operator("mol_H4_doublecation_minao_qubitham_bk.data", data_directory=pwd_this_test+"/data", plain_text=True) # Assert energy returned is as expected for given parameters qcc_ansatz.update_var_params(qcc_var_params) @@ -203,7 +208,7 @@ def test_qmf_qcc_h4_double_cation(self): qcc_ansatz.build_circuit() # Get qubit hamiltonian for energy evaluation - qubit_hamiltonian = qcc_ansatz.qubit_ham + qubit_hamiltonian = load_operator("mol_H4_doublecation_minao_qubitham_bk_updown.data", data_directory=pwd_this_test+"/data", plain_text=True) # Assert energy returned is as expected for given parameters qcc_ansatz.update_var_params(qcc_var_params) diff --git a/tangelo/toolboxes/ansatz_generator/tests/test_uccsd.py b/tangelo/toolboxes/ansatz_generator/tests/test_uccsd.py index 284a357ca..606b1f89c 100644 --- a/tangelo/toolboxes/ansatz_generator/tests/test_uccsd.py +++ b/tangelo/toolboxes/ansatz_generator/tests/test_uccsd.py @@ -14,12 +14,17 @@ import unittest import numpy as np +import os +from openfermion import load_operator from tangelo.molecule_library import mol_H2_sto3g, mol_H4_sto3g, mol_H4_doublecation_minao, mol_H4_cation_sto3g from tangelo.toolboxes.qubit_mappings import jordan_wigner from tangelo.toolboxes.ansatz_generator.uccsd import UCCSD from tangelo.linq import Simulator +# For openfermion.load_operator function. +pwd_this_test = os.path.dirname(os.path.abspath(__file__)) + class UCCSDTest(unittest.TestCase): @@ -133,7 +138,7 @@ def test_uccsd_H4_doublecation(self): uccsd_ansatz.build_circuit() # Build qubit hamiltonian for energy evaluation - qubit_hamiltonian = jordan_wigner(mol_H4_doublecation_minao.fermionic_hamiltonian) + qubit_hamiltonian = load_operator("mol_H4_doublecation_minao_qubitham_jw.data", data_directory=pwd_this_test+"/data", plain_text=True) # Assert energy returned is as expected for given parameters sim = Simulator() diff --git a/tangelo/toolboxes/ansatz_generator/tests/test_upccgsd.py b/tangelo/toolboxes/ansatz_generator/tests/test_upccgsd.py index 01cf460eb..7e10d0523 100644 --- a/tangelo/toolboxes/ansatz_generator/tests/test_upccgsd.py +++ b/tangelo/toolboxes/ansatz_generator/tests/test_upccgsd.py @@ -14,12 +14,17 @@ import unittest import numpy as np +import os +from openfermion import load_operator from tangelo.molecule_library import mol_H2_sto3g, mol_H4_doublecation_minao, mol_H4_cation_sto3g from tangelo.toolboxes.qubit_mappings import jordan_wigner from tangelo.toolboxes.ansatz_generator.upccgsd import UpCCGSD from tangelo.linq import Simulator +# For openfermion.load_operator function. +pwd_this_test = os.path.dirname(os.path.abspath(__file__)) + class UpCCGSDTest(unittest.TestCase): @@ -105,7 +110,7 @@ def test_upccgsd_H4_doublecation(self): upccgsd_ansatz.build_circuit() # Build qubit hamiltonian for energy evaluation - qubit_hamiltonian = jordan_wigner(mol_H4_doublecation_minao.fermionic_hamiltonian) + qubit_hamiltonian = load_operator("mol_H4_doublecation_minao_qubitham_jw.data", data_directory=pwd_this_test+"/data", plain_text=True) # Assert energy returned is as expected for given parameters sim = Simulator() diff --git a/tangelo/toolboxes/measurements/tests/test_measurements.py b/tangelo/toolboxes/measurements/tests/test_measurements.py index a682bc202..3a23d1a3f 100644 --- a/tangelo/toolboxes/measurements/tests/test_measurements.py +++ b/tangelo/toolboxes/measurements/tests/test_measurements.py @@ -14,6 +14,7 @@ import unittest import os +from openfermion import load_operator from tangelo.helpers.utils import default_simulator from tangelo.linq import translator, Simulator, Circuit @@ -79,9 +80,7 @@ def test_measurement_uniform_H2(self): abs_circ = translator._translate_openqasm2abs(openqasm_circ) # Load qubit Hamiltonian - with open(f"{path_data}/H2_qubit_hamiltonian.txt", 'r') as f: - qb_hamstring = f.read() - qb_ham = string_ham_to_of(qb_hamstring) + qb_ham = load_operator("mol_H2_qubitham.data", data_directory=path_data, plain_text=True) # Get exact expectation value using a simulator sim_exact = Simulator() diff --git a/tangelo/toolboxes/measurements/tests/test_qubit_terms_grouping.py b/tangelo/toolboxes/measurements/tests/test_qubit_terms_grouping.py index c800c2d44..4721d4115 100644 --- a/tangelo/toolboxes/measurements/tests/test_qubit_terms_grouping.py +++ b/tangelo/toolboxes/measurements/tests/test_qubit_terms_grouping.py @@ -14,6 +14,7 @@ import unittest import os +from openfermion import load_operator from tangelo.linq import translator, Simulator, Circuit from tangelo.helpers import string_ham_to_of, measurement_basis_gates @@ -32,9 +33,7 @@ def test_qubitwise_commutativity_of_H2(self): """ # Load qubit Hamiltonian - with open(f"{path_data}/H2_qubit_hamiltonian.txt", 'r') as f: - qb_hamstring = f.read() - qb_ham = string_ham_to_of(qb_hamstring) + qb_ham = load_operator("mol_H2_qubitham.data", data_directory=path_data, plain_text=True) # Group Hamiltonian terms using qubitwise commutativity grouped_ops = group_qwc(qb_ham, seed=0) @@ -62,9 +61,7 @@ def test_qubitwise_commutativity_of_H4(self): """ # Load qubit Hamiltonian - with open(f"{path_data}/H4_qubit_hamiltonian.txt", 'r') as f: - qb_hamstring = f.read() - qb_ham = string_ham_to_of(qb_hamstring) + qb_ham = load_operator("mol_H4_qubitham.data", data_directory=path_data, plain_text=True) # Group Hamiltonian terms using qubitwise commutativity grouped_ops = group_qwc(qb_ham, seed=0) diff --git a/tangelo/toolboxes/molecular_computation/frozen_orbitals.py b/tangelo/toolboxes/molecular_computation/frozen_orbitals.py index a2e711694..dc81038e9 100644 --- a/tangelo/toolboxes/molecular_computation/frozen_orbitals.py +++ b/tangelo/toolboxes/molecular_computation/frozen_orbitals.py @@ -13,8 +13,9 @@ # limitations under the License. """This module defines functions to get suggestions for freezing orbitals. Those -functions take a pyscf.gto object and return an integer or a list of orbital -indexes for freezing orbitals. +functions take a molecule and return an integer or a list of orbital indexes for +freezing orbitals. Depending on the function, a Molecule or a +SecondQuantizedMolecule object can be used. """ @@ -23,7 +24,7 @@ def get_frozen_core(molecule): for the core (occupied orbitals). Args: - molecule (pyscf.gto): Molecule to be evaluated. + molecule (SecondQuantizedMolecule): Molecule to be evaluated. Returns: int: First N molecular orbitals to freeze. @@ -50,11 +51,10 @@ def get_orbitals_excluding_homo_lumo(molecule, homo_minus_n=0, lumo_plus_n=0): """Function that returns a list of orbitals to freeze if the user wants to consider only a subset from HOMO(-homo_min_n) to LUMO(+lumo_plus_n) orbitals. Users should be aware of degeneracies, as this function does not - take this property into account. Also, it is only relevant for closed-shell - systems. + take this property into account. Args: - molecule (pyscf.gto): Molecule to be evaluated. + molecule (SecondQuantizedMolecule): Molecule to be evaluated. homo_minus_n (int): Starting point at HOMO - homo_minus_n. lumo_plus_n (int): Ending point at LUMO + lumo_plus_n. @@ -65,11 +65,9 @@ def get_orbitals_excluding_homo_lumo(molecule, homo_minus_n=0, lumo_plus_n=0): # Getting the number of molecular orbitals. It also works with different # basis sets. n_molecular_orb = molecule.n_mos - n_electrons = molecule.n_electrons - # Identify the HOMO and LUMO. - n_homo = n_electrons // 2 - 1 - n_lumo = n_homo + 1 + n_lumo = molecule.mo_occ.tolist().index(0.) + n_homo = n_lumo - 1 frozen_orbitals = [n for n in range(n_molecular_orb) if n not in range(n_homo-homo_minus_n, n_lumo+lumo_plus_n+1)] From 3a40427bb0b25a47ec22d13a26475cba2dac53d7 Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Wed, 29 Dec 2021 23:08:57 +0100 Subject: [PATCH 32/68] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 1d229c349..018aaafed 100644 --- a/README.rst +++ b/README.rst @@ -11,7 +11,7 @@ Tangelo overview |build| .. |maintainer| image:: https://img.shields.io/badge/maintainer-GoodChemistry-blue -.. |licence| image:: https://img.shields.io/badge/License-Apache-green +.. |licence| image:: https://img.shields.io/badge/License-Apache_2.0-green :target: https://github.com/quantumsimulation/QEMIST_qSDK/blob/main/README.rst .. |build| image:: https://github.com/quantumsimulation/QEMIST_qSDK/actions/workflows/github_actions_automated_testing.yml/badge.svg .. |python| image:: From 63a7ab9c13405ce3bb904d572cd27b7ddf1fcb7a Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Wed, 29 Dec 2021 23:09:32 +0100 Subject: [PATCH 33/68] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 018aaafed..5e3c47a40 100644 --- a/README.rst +++ b/README.rst @@ -10,7 +10,7 @@ Tangelo overview |licence| |build| -.. |maintainer| image:: https://img.shields.io/badge/maintainer-GoodChemistry-blue +.. |maintainer| image:: https://img.shields.io/badge/Maintainer-GoodChemistry-blue .. |licence| image:: https://img.shields.io/badge/License-Apache_2.0-green :target: https://github.com/quantumsimulation/QEMIST_qSDK/blob/main/README.rst .. |build| image:: https://github.com/quantumsimulation/QEMIST_qSDK/actions/workflows/github_actions_automated_testing.yml/badge.svg From b7fc9aacbf3d1a8a760caf1be513540fd4f948af Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Wed, 29 Dec 2021 23:17:06 +0100 Subject: [PATCH 34/68] Update and name change of CI workflow --- ...tions_automated_testing.yml => continuous_integration.yml} | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) rename .github/workflows/{github_actions_automated_testing.yml => continuous_integration.yml} (98%) diff --git a/.github/workflows/github_actions_automated_testing.yml b/.github/workflows/continuous_integration.yml similarity index 98% rename from .github/workflows/github_actions_automated_testing.yml rename to .github/workflows/continuous_integration.yml index 66672c42a..99096ad8c 100755 --- a/.github/workflows/github_actions_automated_testing.yml +++ b/.github/workflows/continuous_integration.yml @@ -1,4 +1,4 @@ -name: automated_testing +name: Continuous Integration on: [pull_request] @@ -60,5 +60,3 @@ jobs: cd examples pytest --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html test_notebooks.py if: always() - - From d62a5e3cd4a1b740deccb6b4e210d6d39ee62609 Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Wed, 29 Dec 2021 23:21:44 +0100 Subject: [PATCH 35/68] Update README.rst --- README.rst | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 5e3c47a40..0cdc343d5 100644 --- a/README.rst +++ b/README.rst @@ -1,11 +1,10 @@ -[![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/nasa/hybridq.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/nasa/hybridq/context:python) - .. |PyPI version shields.io| image:: https://img.shields.io/pypi/v/ansicolortags.svg :target: https://pypi.python.org/pypi/ansicolortags/ Tangelo overview ================ +|PyPI version shields.io| |maintainer| |licence| |build| @@ -13,8 +12,7 @@ Tangelo overview .. |maintainer| image:: https://img.shields.io/badge/Maintainer-GoodChemistry-blue .. |licence| image:: https://img.shields.io/badge/License-Apache_2.0-green :target: https://github.com/quantumsimulation/QEMIST_qSDK/blob/main/README.rst -.. |build| image:: https://github.com/quantumsimulation/QEMIST_qSDK/actions/workflows/github_actions_automated_testing.yml/badge.svg -.. |python| image:: +.. |build| image:: https://github.com/quantumsimulation/QEMIST_qSDK/actions/workflows/continuous_integration.yml/badge.svg Welcome ! From ff6f0e0a303b444f8ff21adb1fd4e97af7a4f6a0 Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Wed, 29 Dec 2021 23:22:29 +0100 Subject: [PATCH 36/68] Update README.rst --- README.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.rst b/README.rst index 0cdc343d5..edd8ef12c 100644 --- a/README.rst +++ b/README.rst @@ -1,10 +1,6 @@ -.. |PyPI version shields.io| image:: https://img.shields.io/pypi/v/ansicolortags.svg - :target: https://pypi.python.org/pypi/ansicolortags/ - Tangelo overview ================ -|PyPI version shields.io| |maintainer| |licence| |build| From 8b3be5010284a73007d92c5abf732d215ca4e8e2 Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Mon, 3 Jan 2022 10:29:25 +0100 Subject: [PATCH 37/68] Circuit methods (repetition operator, equality, trim and split methods) (#101) * Gate objects support == and != operators. Circuit objects supports multiplication by a scalar for repetition (*) * Equality and inequality between circuits implemented (== and !=) * Trim and split methods successfully tested * Added function that reindexes qubits. * Freeze qiskit version to make noisy simulation test pass * Fixed error self.ansatz and checking for its value like it did in qcc was returning errors --- .../github_actions_automated_testing.yml | 2 +- tangelo/algorithms/variational/vqe_solver.py | 19 +-- tangelo/linq/__init__.py | 2 +- tangelo/linq/circuit.py | 139 ++++++++++++++++++ tangelo/linq/gate.py | 10 +- tangelo/linq/tests/test_circuits.py | 128 ++++++++++++++-- tangelo/linq/tests/test_gates.py | 9 ++ tangelo/linq/tests/test_simulator.py | 1 - 8 files changed, 286 insertions(+), 24 deletions(-) diff --git a/.github/workflows/github_actions_automated_testing.yml b/.github/workflows/github_actions_automated_testing.yml index f98404d45..3c25d2894 100755 --- a/.github/workflows/github_actions_automated_testing.yml +++ b/.github/workflows/github_actions_automated_testing.yml @@ -37,7 +37,7 @@ jobs: - name: Install backends except qsharp/qdk run: | - pip install qiskit + pip install qiskit==0.33.1 # Due to strange behaviour of noise model pip install qulacs pip install amazon-braket-sdk pip install cirq==0.12.0 diff --git a/tangelo/algorithms/variational/vqe_solver.py b/tangelo/algorithms/variational/vqe_solver.py index 5b955006a..639e3bf3c 100644 --- a/tangelo/algorithms/variational/vqe_solver.py +++ b/tangelo/algorithms/variational/vqe_solver.py @@ -110,18 +110,18 @@ def __init__(self, opt_dict): else: raise KeyError(f"Keyword :: {k}, not available in VQESolver") - # The QCC ansatz requires up_then_down=True when mapping="jw" - if self.ansatz == BuiltInAnsatze.QCC and self.qubit_mapping.lower() == "jw" and not self.up_then_down: - warn_msg = "The QCC ansatz requires spin-orbital ordering to be all spin-up "\ - "first followed by all spin-down for the JW mapping." - warnings.warn(warn_msg, RuntimeWarning) - self.up_then_down = True - # Raise error/warnings if input is not as expected. Only a single input # must be provided to avoid conflicts. if not (bool(self.molecule) ^ bool(self.qubit_hamiltonian)): raise ValueError(f"A molecule OR qubit Hamiltonian object must be provided when instantiating {self.__class__.__name__}.") + # The QCC ansatz requires up_then_down=True when mapping="jw" + if isinstance(self.ansatz, BuiltInAnsatze): + if self.ansatz == BuiltInAnsatze.QCC and self.qubit_mapping.lower() == "jw" and not self.up_then_down: + warnings.warn("The QCC ansatz requires spin-orbital ordering to be all spin-up " + "first followed by all spin-down for the JW mapping.", RuntimeWarning) + self.up_then_down = True + self.optimal_energy = None self.optimal_var_params = None self.builtin_ansatze = set(BuiltInAnsatze) @@ -171,7 +171,7 @@ def build(self): raise ValueError("The system must be reduced to a HOMO-LUMO problem for {} ansatz.".format(self.ansatz)) # Build / set ansatz circuit. Use user-provided circuit or built-in ansatz depending on user input. - if type(self.ansatz) == BuiltInAnsatze: + if isinstance(self.ansatz, BuiltInAnsatze): if self.ansatz == BuiltInAnsatze.UCCSD: self.ansatz = UCCSD(self.molecule, self.qubit_mapping, self.up_then_down) elif self.ansatz == BuiltInAnsatze.UCC1: @@ -190,8 +190,9 @@ def build(self): raise ValueError(f"Unsupported ansatz. Built-in ansatze:\n\t{self.builtin_ansatze}") elif not isinstance(self.ansatz, Ansatz): raise TypeError(f"Invalid ansatz dataype. Expecting instance of Ansatz class, or one of built-in options:\n\t{self.builtin_ansatze}") + # Building with a qubit Hamiltonian. - elif (not isinstance(self.ansatz, Ansatz)): + elif not isinstance(self.ansatz, Ansatz): raise TypeError(f"Invalid ansatz dataype. Expecting a custom Ansatz (Ansatz class).") # Set ansatz initial parameters (default or use input), build corresponding ansatz circuit diff --git a/tangelo/linq/__init__.py b/tangelo/linq/__init__.py index 582bf8ed3..ff07c9066 100644 --- a/tangelo/linq/__init__.py +++ b/tangelo/linq/__init__.py @@ -13,6 +13,6 @@ # limitations under the License. from .gate import * -from .circuit import Circuit +from .circuit import Circuit, stack from .translator import * from .simulator import Simulator, backend_info diff --git a/tangelo/linq/circuit.py b/tangelo/linq/circuit.py index f4c904c4c..05b0408e8 100644 --- a/tangelo/linq/circuit.py +++ b/tangelo/linq/circuit.py @@ -18,7 +18,11 @@ characteristics (width, size ...). """ +import copy from typing import List + +import numpy as np + from tangelo.linq import Gate @@ -68,6 +72,29 @@ def __add__(self, other): """ return Circuit(self._gates + other._gates, n_qubits=max(self.width, other.width)) + def __mul__(self, n_repeat): + """Return a circuit consisting of n_repeat repetitions of the input circuit. + """ + if not isinstance(n_repeat, (int, np.integer)) or n_repeat <= 0: + raise ValueError("Multiplication (repetition) operator with Circuit class only works for integers > 0") + return Circuit(self._gates * n_repeat, n_qubits=self.width) + + def __rmul__(self, n_repeat): + """Return a circuit consisting of n_repeat repetitions of the input circuit (circuit as right-hand side) + """ + return self * n_repeat + + def __eq__(self, other): + """Define equality (==) between 2 circuits. They are equal iff all their gates are equal, and they have + the same numbers of qubits. + """ + return (self._gates == other._gates) and (self.width == other.width) + + def __ne__(self, other): + """Define inequality (!=) operator on circuits + """ + return not (self == other) + @property def size(self): """The size is the number of gates in the circuit. It is different from @@ -130,6 +157,87 @@ def check_index_valid(index): # Keep track of the total gate count self._gate_counts[gate.name] = self._gate_counts.get(gate.name, 0) + 1 + def trim_qubits(self): + """Trim unnecessary qubits and update indices with the lowest values possible. + """ + qubits_in_use = set().union(*self.get_entangled_indices()) + mapping = {ind: i for i, ind in enumerate(qubits_in_use)} + for g in self._gates: + g.target = [mapping[ind] for ind in g.target] + if g.control: + g.control = [mapping[ind] for ind in g.control] + + self._qubit_indices = set(range(len(qubits_in_use))) + return self + + def reindex_qubits(self, new_indices): + """Reindex qubit indices according to users labels / new indices. + """ + + if len(new_indices) != len(self._qubit_indices): + raise ValueError(f"The number of indices does not match the length of self._qubit_indices") + + qubits_in_use = self._qubit_indices + mapping = {i: j for i, j in zip(qubits_in_use, new_indices)} + for g in self._gates: + g.target = [mapping[ind] for ind in g.target] + if g.control: + g.control = [mapping[ind] for ind in g.control] + + self._qubit_indices = set(new_indices) + + def get_entangled_indices(self): + """Return a list of unentangled sets of qubit indices. Each set includes indices + of qubits that form an entangled subset. + """ + + entangled_indices = list() + for g in self._gates: + # Gradually accumulate entangled indices from the different subsets + # Remove and replace them with their union, for each gate. + q_new = set(g.target) if g.control is None else set(g.target + g.control) + for qs in entangled_indices[::-1]: + if q_new & qs: + q_new = q_new | qs + entangled_indices.remove(qs) + entangled_indices.append(q_new) + + return entangled_indices + + def split(self): + """ Split a circuit featuring unentangled qubit subsets into as many circuit objects. + Each circuit only contains the gate operations targeting the qubit indices in its subsets. + + Returns: + list of Circuit: list of resulting circuits + """ + entangled_indices = self.get_entangled_indices() + separate_circuits = [Circuit() for i in range(len(entangled_indices))] + for g in self._gates: + q_new = set(g.target) if g.control is None else set(g.target + g.control) + # Append the gate to the circuit that handles the corresponding qubit indices + for i, indices in enumerate(entangled_indices): + if q_new & indices: + separate_circuits[i].add_gate(g) + break + + # Trim unnecessary indices in the new circuits + for c in separate_circuits: + c.trim_qubits() + return separate_circuits + + def stack(self, *other_circuits): + """Convenience method to stack other circuits on top of this one. + See separate stack function. + + Args: + *other_circuits (Circuit): one or several circuit objects to stack + + Returns: + Circuit: the stacked circuit + """ + return stack(self, *other_circuits) + def inverse(self): """Return the inverse (adjoint) of a circuit @@ -143,3 +251,34 @@ def inverse(self): def serialize(self): return {"type": "QuantumCircuit", "gates": [gate.serialize() for gate in self._gates]} + + +def stack(*circuits): + """ Take list of circuits as input, and stack them (e.g concatenate them along the + width (qubits)) to form a single wider circuit, which allows users to run all of + these circuits at once on a quantum device. + + Stacking provides a way to "fill up" a device if many qubits would be unused otherwise, + therefore reducing cost / duration of a hardware experiment. However, depending on the + device, this may amplify some sources of errors or make qubit placement more challenging. + + Args: + *circuits (Circuit): the circuits to trim and stack into a single one + + Returns: + Circuit: the stacked circuit + """ + + if not circuits: + return Circuit() + + # Trim qubits of input circuit for maximum compactness + circuits = [c.trim_qubits() for c in copy.deepcopy(list(circuits))] + + # Stack circuits. Reindex each circuit with the proper offset and then concatenate, until done + stacked_circuit = circuits.pop(0) + for c in circuits: + c.reindex_qubits(list(range(stacked_circuit.width, stacked_circuit.width + c.width))) + stacked_circuit += c + + return stacked_circuit diff --git a/tangelo/linq/gate.py b/tangelo/linq/gate.py index 3da873183..78988227e 100644 --- a/tangelo/linq/gate.py +++ b/tangelo/linq/gate.py @@ -103,8 +103,16 @@ def __str__(self): return mystr + def __eq__(self, other): + """Define equality (==) operator on gates""" + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Define inequality (!=) operator on gates""" + return not (self == other) + def inverse(self): - """Returns the inverse (adjoint) of a gate. + """Return the inverse (adjoint) of a gate. Return: Gate: the inverse of the gate. diff --git a/tangelo/linq/tests/test_circuits.py b/tangelo/linq/tests/test_circuits.py index 0c6c22ac8..d177d8abc 100644 --- a/tangelo/linq/tests/test_circuits.py +++ b/tangelo/linq/tests/test_circuits.py @@ -21,15 +21,11 @@ import copy from math import pi from collections import Counter -from tangelo.linq import Gate, Circuit +from tangelo.linq import Gate, Circuit, stack # Create several abstract circuits with different features -mygates = list() -mygates.append(Gate("H", 2)) -mygates.append(Gate("CNOT", 1, control=0)) -mygates.append(Gate("CNOT", 2, control=1)) -mygates.append(Gate("Y", 0)) -mygates.append(Gate("RX", 1, parameter=2.)) +mygates = [Gate("H", 2), Gate("CNOT", 1, control=0), Gate("CNOT", 2, control=1), + Gate("Y", 0), Gate("RX", 1, parameter=2.)] circuit1 = Circuit() for gate in mygates: @@ -42,6 +38,10 @@ circuit4 = copy.deepcopy(circuit3) circuit4.add_gate(Gate("RY", 3, parameter="some angle", is_variational=True)) +entangle_circuit = Circuit([Gate("CSWAP", target=[2, 5], control=[0]), + Gate("CSWAP", target=[3, 7], control=[4]), + Gate("H", 6)], n_qubits=10) + class TestCircuits(unittest.TestCase): @@ -84,6 +84,112 @@ def test_add_circuits(self): self.assertTrue(len(circuit_sum._variational_gates) == (len(circuit3._variational_gates) + len(circuit4._variational_gates))) + def test_mul_circuit(self): + """ Test the multiplication (repetition) operator for circuit objects """ + + # Should work for *2 + c2 = circuit3 * 2 + ref_counts = {k: 2*v for k, v in circuit3._gate_counts.items()} + self.assertTrue(c2._gate_counts == ref_counts) + self.assertTrue(len(c2._variational_gates) == 2*len(circuit3._variational_gates)) + + # Fail for incorrect values, such as 2.5, or 0. + with self.assertRaises(ValueError): + _ = circuit3 * 2.5 + with self.assertRaises(ValueError): + _ = circuit3 * 0 + + # Repeating an empty circuit yields an empty circuit + self.assertTrue((Circuit()*3).size == 0) + + # Check on right-hand side + self.assertTrue(2*circuit3 == c2) + + def test_entangled_indices(self): + """ Test that entangled indices subsets are properly updated after + a new gate is added to the circuit. """ + + c = Circuit() + c.add_gate(Gate("CNOT", target=4, control=0)) + self.assertTrue(c.get_entangled_indices() == [{0, 4}]) + c.add_gate(Gate("CNOT", target=5, control=1)) + self.assertTrue(c.get_entangled_indices() == [{0, 4}, {1, 5}]) + c.add_gate(Gate("H", target=2)) + self.assertTrue(c.get_entangled_indices() == [{0, 4}, {1, 5}, {2}]) + c.add_gate(Gate("CNOT", target=6, control=5)) + self.assertTrue(c.get_entangled_indices() == [{0, 4}, {2}, {1, 5, 6}]) + c.add_gate(Gate("CNOT", target=1, control=4)) + self.assertTrue(c.get_entangled_indices() == [{2}, {0, 1, 4, 5, 6}]) + c.add_gate(Gate("CSWAP", target=[2, 7], control=[0])) + self.assertTrue(c.get_entangled_indices() == [{0, 1, 2, 4, 5, 6, 7}]) + + def test_trim_circuit(self): + """ Check that unnecessary indices are trimmed and new indices minimal """ + + ref_c = Circuit([Gate("CSWAP", target=[1, 4], control=[0]), + Gate("CSWAP", target=[2, 6], control=[3]), + Gate("H", 5)], n_qubits=7) + entangle_circuit.trim_qubits() + self.assertTrue(ref_c == entangle_circuit) + + def test_reindex_qubits(self): + """ Test the function that reindexes qubits (e.g replaces indices by another). """ + + # With circuit of natural width + gates = [Gate("H", 2), Gate("CNOT", 1, control=0), Gate("CSWAP", target=[1, 2], control=[0])] + c1 = Circuit(copy.deepcopy(gates)) + c1.reindex_qubits([4, 5, 6]) + + ref = [Gate("H", 6), Gate("CNOT", 5, control=4), Gate("CSWAP", target=[5, 6], control=[4])] + self.assertTrue(ref == c1._gates) + + # With circuit of fixed width (sends 4,5,6 to 0,1,2, the rest is not relevant) + c2 = Circuit(ref, n_qubits=8) + c2.reindex_qubits([3, 4, 5, 6, 0, 1, 2, 7]) + self.assertTrue(gates == c2._gates) + + # Test for input of incorrect length in both previous cases + with self.assertRaises(ValueError): + c1.reindex_qubits([2]) + with self.assertRaises(ValueError): + c2.reindex_qubits([0, 1, 2]) + + def test_split_circuit(self): + """ Test function that splits circuit into several circuits targeting qubit subsets + that are not entangled with each other. Trims unnecessary qubit indices. """ + c = Circuit([Gate("CSWAP", target=[2, 5], control=[0]), + Gate("CSWAP", target=[3, 7], control=[4]), + Gate("H", 6)]) + c1, c2, c3 = c.split() + + self.assertTrue(c1 == Circuit([Gate("CSWAP", target=[1, 2], control=[0])])) + self.assertTrue(c2 == Circuit([Gate("CSWAP", target=[0, 2], control=[1])])) + self.assertTrue(c3 == Circuit([Gate("H", target=0)])) + + def test_stack_circuits(self): + """ Test circuit stacking """ + + c1 = Circuit([Gate("H", 6)]) + c2 = Circuit([Gate("CNOT", 5, control=4)]) + c3 = Circuit([Gate("CSWAP", target=[5, 6], control=[4])]) + + ref = [Gate("H", 0), Gate("CNOT", 2, control=1), Gate("CSWAP", target=[4, 5], control=[3])] + + # No and multiple arguments, natural or as an unpacked list + self.assertTrue(ref == stack(c1, c2, c3)._gates) + self.assertTrue(ref == stack(*[c1, c2, c3])._gates) + self.assertTrue([] == stack(*[])._gates) + + # Try convenience method in Circuit class + self.assertTrue(ref == c1.stack(c2, c3)._gates) + + def test_equality_circuit(self): + """ Test equality operators (== and !=) for circuits """ + self.assertTrue(circuit1 == circuit2) + self.assertTrue(circuit3 != circuit2) + c3 = Circuit(circuit3._gates, n_qubits=6) + self.assertTrue(circuit3 != c3) + def test_fixed_sized_circuit_above(self): """ If circuit is instantiated with fixed width, the code must throw if qubit indices are not consistent """ circuit_fixed = Circuit(n_qubits=2) @@ -94,13 +200,13 @@ def test_fixed_sized_circuit_above(self): def test_fixed_sized_circuit_below(self): """ If circuit is instantiated with fixed width, then the width property must be consistent, regardless - of what gate instructions have been passed """ + of what gate instructions have been passed """ n_qubits = 3 circuit_fixed = Circuit([Gate("H", 0)], n_qubits=n_qubits) self.assertTrue(circuit_fixed.width == n_qubits) def test_inverse(self): - """ Test if inverse function returns the proper set of gates by comparing print strings.""" + """ Test if inverse function returns the proper set of gates.""" mygates_inverse = list() mygates_inverse.append(Gate("RX", 1, parameter=-2.)) @@ -109,11 +215,11 @@ def test_inverse(self): mygates_inverse.append(Gate("CNOT", 1, control=0)) mygates_inverse.append(Gate("H", 2)) circuit1_inverse = Circuit(mygates_inverse) - self.assertTrue(circuit1.inverse().__str__(), circuit1_inverse.__str__()) + self.assertTrue(circuit1.inverse(), circuit1_inverse) ts_circuit = Circuit([Gate("T", 0), Gate("S", 1)]) ts_circuit_inverse = Circuit([Gate("PHASE", 0, parameter=-pi/4), Gate("PHASE", 0, parameter=-pi/2)]) - self.assertTrue(ts_circuit.inverse().__str__(), ts_circuit_inverse.__str__()) + self.assertTrue(ts_circuit.inverse(), ts_circuit_inverse) if __name__ == "__main__": diff --git a/tangelo/linq/tests/test_gates.py b/tangelo/linq/tests/test_gates.py index 415a67973..934e91c52 100644 --- a/tangelo/linq/tests/test_gates.py +++ b/tangelo/linq/tests/test_gates.py @@ -75,6 +75,15 @@ def test_integer_types(self): self.assertRaises(ValueError, Gate, "CSWAP", target=[0, 'a'], control=np.array([1], dtype=np.int32)) self.assertRaises(ValueError, Gate, "X", target=0, control=[-1, 2, 3],) + def test_gate_equality(self): + """ Test behaviour of == and != operators on gates """ + g1 = Gate("CPOTATO", target=2, control=0, parameter=0, is_variational=True) + g2 = Gate("CPOTATO", target=2, control=0, parameter="", is_variational=True) + g3 = Gate("CPOTATO", target=2, control=0, parameter=0, is_variational=True) + + self.assertTrue(g1 == g3) + self.assertTrue(g1 != g2) + if __name__ == "__main__": unittest.main() diff --git a/tangelo/linq/tests/test_simulator.py b/tangelo/linq/tests/test_simulator.py index 632bdb888..6b8297627 100644 --- a/tangelo/linq/tests/test_simulator.py +++ b/tangelo/linq/tests/test_simulator.py @@ -26,7 +26,6 @@ from tangelo.linq import Gate, Circuit, translator, Simulator from tangelo.linq.gate import PARAMETERIZED_GATES -from tangelo.linq.helpers import string_ham_to_of from tangelo.helpers.utils import installed_simulator, installed_sv_simulator, installed_backends path_data = os.path.dirname(os.path.abspath(__file__)) + '/data' From 15b9bf69160a232afec482ba854196480078d4ba Mon Sep 17 00:00:00 2001 From: JamesB-1qbit <84878946+JamesB-1qbit@users.noreply.github.com> Date: Mon, 3 Jan 2022 05:22:08 -0500 Subject: [PATCH 38/68] Staged controlled time (#100) * added qite and projective folder * separated statevector propagation, style improvements * added decomposed versions of controlled swap * added xx gate * added option to not swap qft registers Co-authored-by: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> --- tangelo/algorithms/projective/__init__.py | 15 + .../projective/quantum_imaginary_time.py | 315 ++++++++++++++++ .../algorithms/projective/tests/__init__.py | 13 + .../algorithms/projective/tests/test_qite.py | 158 ++++++++ .../ansatz_generator/adapt_ansatz.py | 6 +- .../ansatz_generator/ansatz_utils.py | 343 +++++++++++++++++- tangelo/toolboxes/ansatz_generator/qcc.py | 4 +- .../tests/test_ansatz_util.py | 301 +++++++++++++++ tangelo/toolboxes/ansatz_generator/uccsd.py | 4 +- tangelo/toolboxes/ansatz_generator/upccgsd.py | 4 +- 10 files changed, 1150 insertions(+), 13 deletions(-) create mode 100644 tangelo/algorithms/projective/__init__.py create mode 100644 tangelo/algorithms/projective/quantum_imaginary_time.py create mode 100644 tangelo/algorithms/projective/tests/__init__.py create mode 100644 tangelo/algorithms/projective/tests/test_qite.py create mode 100644 tangelo/toolboxes/ansatz_generator/tests/test_ansatz_util.py diff --git a/tangelo/algorithms/projective/__init__.py b/tangelo/algorithms/projective/__init__.py new file mode 100644 index 000000000..a597fe080 --- /dev/null +++ b/tangelo/algorithms/projective/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .quantum_imaginary_time import QITESolver diff --git a/tangelo/algorithms/projective/quantum_imaginary_time.py b/tangelo/algorithms/projective/quantum_imaginary_time.py new file mode 100644 index 000000000..f6c47db83 --- /dev/null +++ b/tangelo/algorithms/projective/quantum_imaginary_time.py @@ -0,0 +1,315 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Module that defines the Quantum Imaginary Time Algorithm (QITE) +""" +from copy import copy +import math + +from openfermion import FermionOperator as ofFermionOperator +import numpy as np + +from tangelo.toolboxes.ansatz_generator.ansatz_utils import trotterize +from tangelo.toolboxes.operators.operators import FermionOperator, QubitOperator +from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping +from tangelo.toolboxes.ansatz_generator._general_unitary_cc import uccgsd_generator as uccgsd_pool +from tangelo.toolboxes.operators import qubitop_to_qubitham +from tangelo.toolboxes.qubit_mappings.statevector_mapping import get_reference_circuit +from tangelo.linq import Circuit, Simulator + + +class QITESolver: + """QITE class. This is an iterative algorithm that obtains a unitary operator + that approximates the imaginary time evolution of an initial state. + + Attributes: + molecule (SecondQuantizedMolecule): The molecular system. + dt (float): The imaginary time step size + min_de (float): Maximum energy change allowed before convergence. + max_cycles (int): Maximum number of iterations of QITE. + pool (func): Function that returns a list of FermionOperator. Each + element represents an excitation/operator that has an effect on the + total energy. + pool_args (tuple) : The arguments for the pool function given as a + tuple. + qubit_mapping (str): One of the supported qubit mapping identifiers. + qubit_hamiltonian (QubitOperator-like): Self-explanatory. + up_then_down (bool): Spin orbitals ordering. + n_spinorbitals (int): Self-explanatory. + n_electrons (int): Self-explanatory. + backend_options (dict): Backend options for the underlying QITE propagation + verbose (bool): Flag for verbosity of QITE. + """ + + def __init__(self, opt_dict): + + default_backend_options = {"target": None, "n_shots": None, "noise_model": None} + default_options = {"molecule": None, + "dt": 0.5, "max_cycles": 100, + "min_de": 1.e-7, + "pool": uccgsd_pool, + "pool_args": None, + "frozen_orbitals": "frozen_core", + "qubit_mapping": "jw", + "qubit_hamiltonian": None, + "up_then_down": False, + "n_spinorbitals": None, + "n_electrons": None, + "backend_options": default_backend_options, + "verbose": True} + + # Initialize with default values + self.__dict__ = default_options + # Overwrite default values with user-provided ones, if they correspond to a valid keyword + for k, v in opt_dict.items(): + if k in default_options: + setattr(self, k, v) + else: + raise KeyError(f"Keyword :: {k}, not available in {self.__class__.__name__}") + + # Raise error/warnings if input is not as expected. Only a single input + # must be provided to avoid conflicts. + if not (bool(self.molecule) ^ bool(self.qubit_hamiltonian)): + raise ValueError("A molecule OR qubit Hamiltonian object must be provided when instantiating " + f"{self.__class__.__name__}.") + + if self.qubit_hamiltonian: + if not (self.n_spinorbitals and self.n_electrons): + raise ValueError("Expecting the number of spin-orbitals (n_spinorbitals) and the number of " + "electrons (n_electrons) with a qubit_hamiltonian.") + + self.iteration = 0 + self.energies = list() + + self.circuit_list = list() + self.final_energy = None + self.final_circuit = None + self.final_statevector = None + + self.backend = None + + def prepare_reference_state(self): + """Returns circuit preparing the reference state of the ansatz (e.g + prepare reference wavefunction with HF, multi-reference state, etc). + These preparations must be consistent with the transform used to obtain + the qubit operator. + """ + + return get_reference_circuit(n_spinorbitals=self.n_spinorbitals, + n_electrons=self.n_electrons, + mapping=self.qubit_mapping, + up_then_down=self.up_then_down, + spin=0) + + def build(self): + """Builds the underlying objects required to run the QITE algorithm.""" + + # Building molecule data with a pyscf molecule. + if self.molecule: + + self.n_spinorbitals = self.molecule.n_active_sos + self.n_electrons = self.molecule.n_active_electrons + + # Compute qubit hamiltonian for the input molecular system + qubit_op = fermion_to_qubit_mapping(fermion_operator=self.molecule.fermionic_hamiltonian, + mapping=self.qubit_mapping, + n_spinorbitals=self.n_spinorbitals, + n_electrons=self.n_electrons, + up_then_down=self.up_then_down, + spin=0) + + self.qubit_hamiltonian = qubitop_to_qubitham(qubit_op, self.qubit_mapping, self.up_then_down) + + # Getting the pool of operators for the ansatz. If more functionalities + # are added, this part must be modified and generalized. + if self.pool_args is None: + if self.pool == uccgsd_pool: + self.pool_args = (self.n_spinorbitals,) + else: + raise KeyError("pool_args must be defined if using own pool function") + # Check if pool function returns a QubitOperator or FermionOperator and populate variables + pool_list = self.pool(*self.pool_args) + if isinstance(pool_list[0], QubitOperator): + self.pool_type = "qubit" + self.full_pool_operators = pool_list + elif isinstance(pool_list[0], (FermionOperator, ofFermionOperator)): + self.pool_type = "fermion" + self.fermionic_operators = pool_list + self.full_pool_operators = [fermion_to_qubit_mapping(fermion_operator=fi, + mapping=self.qubit_mapping, + n_spinorbitals=self.n_spinorbitals, + n_electrons=self.n_electrons, + up_then_down=self.up_then_down) for fi in self.fermionic_operators] + else: + raise ValueError("pool function must return either QubitOperator or FermionOperator") + + # Cast all coefs to floats (rotations angles are real). + for qubit_op in self.full_pool_operators: + for term, coeff in qubit_op.terms.items(): + qubit_op.terms[term] = math.copysign(1., coeff.imag) + + # Remove duplicates and only select terms with odd number of Y gates for all mappings except JKMN + if self.qubit_mapping.upper() != "JKMN": + reduced_pool_terms = set() + for qubit_op in self.full_pool_operators: + for term in qubit_op.terms: + count_y = str(term).count("Y") + if count_y % 2 == 1: + reduced_pool_terms.add(term) + else: + reduced_pool_terms = set() + for qubit_op in self.full_pool_operators: + for term in qubit_op.terms.keys(): + if term: + reduced_pool_terms.add(term) + + # Generated list of pool_operators and full pool operator. + self.pool_operators = [QubitOperator(term) for term in reduced_pool_terms] + self.pool_qubit_op = QubitOperator() + for term in self.pool_operators: + self.pool_qubit_op += term + + self.qubit_operator = self.qubit_hamiltonian.to_qubitoperator() + + # Obtain all qubit terms that need to be measured + self.pool_h = [element*self.qubit_operator for element in self.pool_operators] + self.pool_pool = [[element1*element2 for element2 in self.pool_operators] for element1 in self.pool_operators] + + # Obtain initial state preparation circuit + self.circuit_list.append(self.prepare_reference_state()) + self.final_circuit = copy(self.circuit_list[0]) + + # Quantum circuit simulation backend options + self.backend = Simulator(target=self.backend_options["target"], n_shots=self.backend_options["n_shots"], + noise_model=self.backend_options["noise_model"]) + + self.use_statevector = self.backend.statevector_available and self.backend._noise_model is None + + def simulate(self): + """Performs the QITE cycles. Each iteration, a linear system is + solved to obtain the next unitary. The system to be solved can be found in + section 3.5 of https://arxiv.org/pdf/2108.04413.pdf + + Returns: + float: final energy after obtaining running QITE + """ + + # Construction of the circuit. self.max_cycles terms are added, unless + # the energy change is less than self.min_de. + if self.use_statevector: + self.update_statevector(self.backend, self.circuit_list[0]) + self.final_energy = self.energy_expectation(self.backend) + self.energies.append(self.final_energy) + while self.iteration < self.max_cycles: + self.iteration += 1 + if self.verbose: + print(f"Iteration {self.iteration} of QITE with starting energy {self.final_energy}") + + suv, bu = self.calculate_matrices(self.backend, self.final_energy) + + alphas_array = np.linalg.solve(suv.real, bu.real) + # convert to dictionary with key as first (only) term of each pool_operator and value self.dt * alphas_array[i] + alphas_dict = {next(iter(qu_op.terms)): self.dt * alphas_array[i] for i, qu_op in enumerate(self.pool_operators)} + next_circuit = trotterize(self.pool_qubit_op, alphas_dict, trotter_order=1, n_trotter_steps=1) + + self.circuit_list.append(next_circuit) + self.final_circuit += next_circuit + + if self.use_statevector: + self.update_statevector(self.backend, self.circuit_list[self.iteration]) + + new_energy = self.energy_expectation(self.backend) + self.energies.append(new_energy) + + if abs(new_energy - self.final_energy) < self.min_de and self.iteration > 1: + self.final_energy = new_energy + break + self.final_energy = new_energy + + if self.verbose: + print(f"Final energy of QITE is {self.final_energy}") + + return self.energies[-1] + + def update_statevector(self, backend: Simulator, next_circuit: Circuit): + r"""Update self.final_statevector by propagating with next_circuit using backend + + Args: + Simulator: the backend to use for the statevector update + Circuit: The circuit to apply to the statevector + """ + _, self.final_statevector = backend.simulate(next_circuit, + return_statevector=True, + initial_statevector=self.final_statevector) + + def calculate_matrices(self, backend: Simulator, new_energy: float): + r"""Calculated matrix elements for imaginary time evolution. + The matrices are defined in section 3.5 of https://arxiv.org/pdf/2108.04413.pdf + + Args: + backend (Simulator): the backend from which the matrices are generated + new_energy (float): the current energy_expectation of the Hamiltonian + + Returns: + matrix float: The expectation values <\psi| pu^+ pv |\psi> + array float: The expecation values <\psi| pu^+ H |\psi> + """ + + circuit = Circuit(n_qubits=self.final_circuit.width) if self.use_statevector else self.final_circuit + + ndeltab = np.sqrt(1 - 2 * self.dt * new_energy) + prefac = -1j/ndeltab + bu = [prefac*backend.get_expectation_value(element, circuit, initial_statevector=self.final_statevector) + for element in self.pool_h] + bu = np.array(bu) + pool_size = len(self.pool_h) + suv = np.zeros((pool_size, pool_size), dtype=complex) + for u in range(pool_size): + for v in range(u+1, pool_size): + suv[u, v] = backend.get_expectation_value(self.pool_pool[u][v], + circuit, + initial_statevector=self.final_statevector) + suv[v, u] = suv[u, v] + + return suv, bu + + def energy_expectation(self, backend: Simulator): + """Estimate energy using the self.final_circuit, qubit hamiltonian and compute + backend. + + Args: + backend (Simulator): the backend one computes the energy expectation with + + Returns: + float: energy computed by the backend + """ + circuit = Circuit(n_qubits=self.final_circuit.width) if self.use_statevector else self.final_circuit + energy = backend.get_expectation_value(self.qubit_hamiltonian.to_qubitoperator(), + circuit, + initial_statevector=self.final_statevector) + return energy + + def get_resources(self): + """Returns resources currently used in underlying state preparation i.e. self.final_circuit + the number of pool operators, and the size of qubit_hamiltonian + + Returns: + dict: Dictionary of various quantum resources required""" + resources = dict() + resources["qubit_hamiltonian_terms"] = len(self.qubit_hamiltonian.terms) + resources["pool_size"] = len(self.pool_operators) + resources["circuit_width"] = self.final_circuit.width + resources["circuit_gates"] = self.final_circuit.size + resources["circuit_2qubit_gates"] = self.final_circuit.counts.get("CNOT", 0) + return resources diff --git a/tangelo/algorithms/projective/tests/__init__.py b/tangelo/algorithms/projective/tests/__init__.py new file mode 100644 index 000000000..532746351 --- /dev/null +++ b/tangelo/algorithms/projective/tests/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/tangelo/algorithms/projective/tests/test_qite.py b/tangelo/algorithms/projective/tests/test_qite.py new file mode 100644 index 000000000..c9f909bbc --- /dev/null +++ b/tangelo/algorithms/projective/tests/test_qite.py @@ -0,0 +1,158 @@ +# Copyright 2021 Good Chemsitry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest + +from tangelo.algorithms.projective.quantum_imaginary_time import QITESolver +from tangelo.molecule_library import mol_H2_sto3g, mol_H4_sto3g +from tangelo.linq.noisy_simulation import NoiseModel + + +class QITESolverTest(unittest.TestCase): + + def test_instantiation_qite(self): + """Try instantiating QITESolver with basic input.""" + + options = {"molecule": mol_H2_sto3g, "qubit_mapping": "jw"} + QITESolver(options) + + def test_instantiation_qite_incorrect_keyword(self): + """Instantiating with an incorrect keyword should return an error """ + + options = {"molecule": mol_H2_sto3g, "qubit_mapping": "jw", "dummy": True} + self.assertRaises(KeyError, QITESolver, options) + + def test_instantiation_qite_missing_molecule(self): + """Instantiating with no molecule should return an error.""" + + options = {"qubit_mapping": "jw"} + self.assertRaises(ValueError, QITESolver, options) + + def test_simulate_h2_noisy(self): + """Run QITE on H2 molecule with bk qubit mapping and an empty noise model for 1 cycle. + Result should be lower than mean field energy. + """ + + backend_options = {"target": None, "n_shots": 10000, "noise_model": NoiseModel()} + + qite_options = {"molecule": mol_H2_sto3g, "qubit_mapping": "scbk", + "verbose": True, "backend_options": backend_options, + "max_cycles": 1, "up_then_down": True} + qite_solver = QITESolver(qite_options) + qite_solver.build() + + energy = qite_solver.simulate() + self.assertTrue(energy < mol_H2_sto3g.mf_energy) + + def test_simulate_h2(self): + """Run QITE on H2 molecule, with JW qubit mapping and exact simulator + """ + + qite_options = {"molecule": mol_H2_sto3g, "qubit_mapping": "jw", + "verbose": False, "up_then_down": True} + qite_solver = QITESolver(qite_options) + qite_solver.build() + + energy = qite_solver.simulate() + self.assertAlmostEqual(energy, -1.137270422018, delta=1e-4) + + def test_resources_h2(self): + """Test get_resources funtion for QITE on H2 molecule, with JW qubit mapping. + """ + + qite_options = {"molecule": mol_H2_sto3g, "qubit_mapping": "jw", + "verbose": False} + qite_solver = QITESolver(qite_options) + qite_solver.build() + resources = qite_solver.get_resources() + self.assertEqual(resources["qubit_hamiltonian_terms"], 15) + self.assertEqual(resources["pool_size"], 20) + + def test_mapping_BK(self): + """Test that BK mapping recovers the expected result for the example of H2. + """ + qite_options = {"molecule": mol_H2_sto3g, "verbose": False, + "qubit_mapping": "bk"} + + qite_solver = QITESolver(qite_options) + qite_solver.build() + energy = qite_solver.simulate() + + energy_target = -1.137270 + self.assertAlmostEqual(energy, energy_target, places=5) + + def test_mapping_JKMN(self): + """Test that JKMN mapping recovers the expected result for the example of H2. + """ + qite_options = {"molecule": mol_H2_sto3g, "verbose": False, + "qubit_mapping": "JKMN"} + + qite_solver = QITESolver(qite_options) + qite_solver.build() + energy = qite_solver.simulate() + + energy_target = -1.137270 + self.assertAlmostEqual(energy, energy_target, places=5) + + def test_mapping_scBK(self): + """Test that scBK mapping recovers the expected result for the example of H2. + """ + qite_options = {"molecule": mol_H2_sto3g, "verbose": False, + "qubit_mapping": "scbk", "up_then_down": True} + + qite_solver = QITESolver(qite_options) + qite_solver.build() + energy = qite_solver.simulate() + + energy_target = -1.137270 + self.assertAlmostEqual(energy, energy_target, places=5) + + def test_spin_reorder_equivalence(self): + """Test that re-ordered spin input (all up followed by all down) returns + the same optimized energy result for both JW and BK mappings. + """ + qite_options = {"molecule": mol_H2_sto3g, "up_then_down": True, + "verbose": False, "qubit_mapping": "jw"} + + qite_solver_jw = QITESolver(qite_options) + qite_solver_jw.build() + energy_jw = qite_solver_jw.simulate() + + qite_options["qubit_mapping"] = "bk" + qite_solver_bk = QITESolver(qite_options) + qite_solver_bk.build() + energy_bk = qite_solver_bk.simulate() + + energy_target = -1.137270 + self.assertAlmostEqual(energy_jw, energy_target, places=5) + self.assertAlmostEqual(energy_bk, energy_target, places=5) + + def test_simulate_h4_frozen_orbitals(self): + """Run QITE on H4 molecule, with UCCSD ansatz, JW qubit mapping, initial + parameters, exact simulator. First (occupied) and last (virtual) + orbitals are frozen. + """ + mol_H4_sto3g_frozen = mol_H4_sto3g.freeze_mos([0, 3], inplace=False) + + qite_options = {"molecule": mol_H4_sto3g_frozen, "qubit_mapping": "jw", + "verbose": False} + qite_solver = QITESolver(qite_options) + qite_solver.build() + + energy = qite_solver.simulate() + self.assertAlmostEqual(energy, -1.8943598012229799, delta=1e-5) + + +if __name__ == "__main__": + unittest.main() diff --git a/tangelo/toolboxes/ansatz_generator/adapt_ansatz.py b/tangelo/toolboxes/ansatz_generator/adapt_ansatz.py index c4531a5d6..94a3c5d63 100644 --- a/tangelo/toolboxes/ansatz_generator/adapt_ansatz.py +++ b/tangelo/toolboxes/ansatz_generator/adapt_ansatz.py @@ -18,7 +18,7 @@ from tangelo.linq import Circuit from tangelo.toolboxes.qubit_mappings.statevector_mapping import get_reference_circuit -from tangelo.toolboxes.ansatz_generator.ansatz_utils import pauliword_to_circuit +from tangelo.toolboxes.ansatz_generator.ansatz_utils import exp_pauliword_to_gates from tangelo.toolboxes.ansatz_generator.ansatz import Ansatz from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping @@ -118,7 +118,7 @@ def build_circuit(self, var_params=None): for op in self.operators: for pauli_term in op.get_operators(): pauli_tuple = list(pauli_term.terms.keys())[0] - adapt_circuit += pauliword_to_circuit(pauli_tuple, 0.1) + adapt_circuit += exp_pauliword_to_gates(pauli_tuple, 0.1) adapt_circuit = Circuit(adapt_circuit) if adapt_circuit.size != 0: @@ -156,6 +156,6 @@ def add_operator(self, pauli_operator, ferm_operator=None): self._var_params_prefactor += [math.copysign(1., coeff)] pauli_tuple = list(pauli_term.terms.keys())[0] - new_operator = Circuit(pauliword_to_circuit(pauli_tuple, 0.1)) + new_operator = Circuit(exp_pauliword_to_gates(pauli_tuple, 0.1)) self.circuit += new_operator diff --git a/tangelo/toolboxes/ansatz_generator/ansatz_utils.py b/tangelo/toolboxes/ansatz_generator/ansatz_utils.py index b2b546fda..a9a2a4c16 100644 --- a/tangelo/toolboxes/ansatz_generator/ansatz_utils.py +++ b/tangelo/toolboxes/ansatz_generator/ansatz_utils.py @@ -17,8 +17,17 @@ facilitate the assembly of ansatz quantum circuits. """ +from copy import deepcopy +from itertools import combinations + import numpy as np +from openfermion.ops import FermionOperator as ofFermionOperator +from openfermion.ops import InteractionOperator as ofInteractionOperator +from openfermion.ops import QubitOperator as ofQubitOperator + from tangelo.linq import Circuit, Gate +from tangelo.toolboxes.operators import FermionOperator, QubitOperator +from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping, get_fermion_operator def pauli_op_to_gate(index, op, inverse=False): @@ -32,9 +41,18 @@ def pauli_op_to_gate(index, op, inverse=False): return Gate("RX", index, parameter=angle) if not inverse else Gate("RX", index, parameter=-angle+4*np.pi) -def pauliword_to_circuit(pauli_word, coef, variational=True): - """Generates a quantum circuit corresponding to the pauli word, as described - in Whitfield 2010 (https://arxiv.org/pdf/1001.3855.pdf). +def exp_pauliword_to_gates(pauli_word, coef, variational=True, control=None): + """Generate a list of Gate objects corresponding to the exponential of a pauli word. + The process is described in Whitfield 2010 https://arxiv.org/pdf/1001.3855.pdf + + Args: + pauli_word (tuple): Openfermion-like tuple that generates a pauli_word to exponentiate + coef (float): The coefficient in the exponentiation + variational (bool): When creating the Gate objects, label the (controlled-)Rz gate as variational + control (integer): The control qubit label + + Returns: + list: list of Gate objects that represents the exponentiation of the pauli word. """ gates = [] @@ -49,7 +67,10 @@ def pauliword_to_circuit(pauli_word, coef, variational=True): gates += cnot_ladder_gates angle = 2.*coef if coef >= 0. else 4*np.pi+2*coef - gates += [Gate("RZ", target=indices[-1], parameter=angle, is_variational=variational)] + if control is None: + gates += [Gate("RZ", target=indices[-1], parameter=angle, is_variational=variational)] + else: + gates += [Gate("CRZ", target=indices[-1], control=control, parameter=angle)] gates += cnot_ladder_gates[::-1] @@ -59,3 +80,317 @@ def pauliword_to_circuit(pauli_word, coef, variational=True): gates += [pauli_op_to_gate(index, op, inverse=True)] return gates + + +def get_exponentiated_qubit_operator_circuit(qubit_op, time=1., variational=False, trotter_order=1, control=None, + return_phase=False): + """Generate the exponentiation of a qubit operator in first- or second-order Trotterized form. + The algorithm is described in Whitfield 2010 https://arxiv.org/pdf/1001.3855.pdf + + Args: + qubit_op (QubitOperator): qubit hamiltonian to exponentiate + time (float or dict): The time to evolve the whole system or individiual times for each + term in the operator. If a dictionary, must have keys that have a matching key in qubit_op.terms + variational (bool) : Whether the coefficients are variational + trotter_order (int): order of trotter approximation, only 1 or 2 are supported. + return_phase (bool): Return the global-phase generated + + Returns: + Circuit: circuit corresponding to exponentiation of qubit operator + phase : The global phase of the time evolution if return_phase=True else not included + """ + pauli_words = list(qubit_op.terms.items()) + + if trotter_order > 2: + raise ValueError(f"Trotter order of >2 is not supported currently in Tangelo.") + prefactor = 1/2 if trotter_order == 2 else 1 + + if isinstance(time, (float, np.floating, np.integer, int)): + evolve_time = {term: prefactor*time for term in qubit_op.terms.keys()} + elif isinstance(time, dict): + if time.keys() == qubit_op.terms.keys(): + evolve_time = {term: prefactor*etime for term, etime in time.items()} + else: + raise ValueError(f"The keys in the time dictionary do not match the keys in qubit_op.terms") + else: + raise ValueError(f"time must be a float or a dictionary") + + phase = 1. + exp_pauli_word_gates = list() + for i in range(trotter_order): + if i == 0: + pauli_words.reverse() + for pauli_word, coef in pauli_words: + if pauli_word: # identity terms do not contribute to evolution outside of a phase + if abs(np.real(coef)*evolve_time[pauli_word]) > 1.e-10: + exp_pauli_word_gates += exp_pauliword_to_gates(pauli_word, + np.real(coef)*evolve_time[pauli_word], + variational=variational, + control=control) + else: + if control is None: + phase *= np.exp(-1j * coef * evolve_time[pauli_word]) + else: + exp_pauli_word_gates += [Gate("PHASE", target=control, parameter=-np.real(coef)*evolve_time[pauli_word])] + + return_value = (Circuit(exp_pauli_word_gates), phase) if return_phase else Circuit(exp_pauli_word_gates) + return return_value + + +def trotterize(operator, time=1., n_trotter_steps=1, trotter_order=1, variational=False, + mapping_options=dict(), control=None, return_phase=False): + """Generate the circuit that represents time evolution of an operator. + This circuit is generated as a trotterization of a qubit operator which is either the input + or mapped from the given fermion operator. + + Args: + operator (QubitOperator or FermionOperator): operator to time evolve + time (float or dict): The time to evolve the whole system or individiual times for each + term in the operator. If a dict, each key must match the keys in operator.terms + variational (bool): whether the coefficients are variational + trotter_order (int): order of trotter approximation, 1 or 2 supported + n_trotter_steps (int): The number of different time steps taken for total time t + mapping_options (dict): Defines the desired Fermion->Qubit mapping + Default values:{"up_then_down": False, "qubit_mapping": "jw", "n_spinorbitals": None, + "n_electrons": None} + control (int): The label for the control Qubit of the time-evolution + return_phase (bool): If return_phase is True, the global phase of the time-evolution will be returned + + Returns: + Circuit: circuit corresponding to time evolution of the operator + float: the global phase not included in the circuit if return_phase=True else not included + """ + + if isinstance(operator, ofFermionOperator): + options = {"up_then_down": False, "qubit_mapping": "jw", "n_spinorbitals": None, "n_electrons": None} + # Overwrite default values with user-provided ones, if they correspond to a valid keyword + for k, v in mapping_options.items(): + if k in options: + options[k] = v + else: + raise KeyError(f"Keyword :: {k}, not a valid fermion to qubit mapping option") + if isinstance(time, (float, np.floating, int, np.integer)): + evolve_time = {term: time for term in operator.terms.keys()} + elif isinstance(time, dict): + if time.keys() == operator.terms.keys(): + evolve_time = deepcopy(time) + else: + raise ValueError(f"keys of time do not match keys of operator.terms") + else: + raise ValueError("time must be a float or dictionary of floats") + new_operator = FermionOperator() + for term in operator.terms: + new_operator += FermionOperator(term, operator.terms[term]*evolve_time[term]/n_trotter_steps) + qubit_op = fermion_to_qubit_mapping(fermion_operator=new_operator, + mapping=options["qubit_mapping"], + n_spinorbitals=options["n_spinorbitals"], + n_electrons=options["n_electrons"], + up_then_down=options["up_then_down"]) + circuit, phase = get_exponentiated_qubit_operator_circuit(qubit_op, + time=1., # time is already included + trotter_order=trotter_order, + variational=variational, + control=control, + return_phase=True) + + elif isinstance(operator, (ofQubitOperator)): + qubit_op = deepcopy(operator) + if isinstance(time, float): + evolve_time = time / n_trotter_steps + elif isinstance(time, dict): + if time.keys() == operator.terms.keys(): + evolve_time = {term: etime / n_trotter_steps for term, etime in time.items()} + else: + raise ValueError(f"time dictionary and operator.terms dictionary have different keys.") + else: + raise ValueError(f"time must be a float or a dictionary of floats") + circuit, phase = get_exponentiated_qubit_operator_circuit(qubit_op, + time=evolve_time, + trotter_order=trotter_order, + variational=variational, + control=control, + return_phase=True) + else: + raise ValueError("Only FermionOperator or QubitOperator allowed") + + if n_trotter_steps == 1: + return_value = (circuit, phase) if return_phase else circuit + else: + final_circuit = deepcopy(circuit) + final_phase = deepcopy(phase) + for i in range(1, n_trotter_steps): + final_circuit += circuit + final_phase *= phase + return_value = (final_circuit, final_phase) if return_phase else circuit + return return_value + + +def append_qft_rotations_gates(gate_list, qubit_list, prefac=1): + """Appends the list of gates required for a quantum fourier transform to a gate list. + + Args: + gate_list (list): List of Gate elements + qubit_list (list): List of integers for which the qft operations are performed + + Returns: + list: List of Gate objects for rotation portion of qft circuit appended to gate_list""" + n = len(qubit_list) + if n == 0: + return gate_list + n -= 1 + gate_list += [Gate("H", target=qubit_list[n])] + for i, qubit in enumerate(qubit_list[:n]): + gate_list += [Gate("CPHASE", control=qubit, target=qubit_list[n], parameter=prefac*np.pi/2**(n-i))] + + append_qft_rotations_gates(gate_list, qubit_list[:n], prefac=prefac) + + +def swap_registers(gate_list, qubit_list): + """Function to swap register order. + Args: + gate_list (list): List of Gate + qubit_list (list): List of integers for the locations of the qubits + + Result: + list: The Gate operations that swap the register order""" + n = len(qubit_list) + for qubit_index in range(n//2): + gate_list += [Gate("SWAP", target=[qubit_list[qubit_index], qubit_list[n - qubit_index - 1]])] + return gate_list + + +def get_qft_circuit(qubits, n_qubits=None, inverse=False, swap=True): + """Returns the QFT or iQFT circuit given a list of qubits to act on. + + Args: + qubits (int or list): The list of qubits to apply the QFT circuit to. If an integer, + the operation is applied to the [0,...,qubits-1] qubits + n_qubits: Argument to initialize a Circuit with the desired number of qubits. + inverse (bool): If True, the inverse QFT is applied. If False, QFT is applied + swap (bool): Whether to apply swap to the registers. + + Returns: + Circuit: The circuit that applies QFT or iQFT to qubits + """ + + if isinstance(qubits, int): + qubit_list = list(range(qubits)) + elif isinstance(qubits, list): + qubit_list = qubits + else: + raise KeyError("qubits must be an int or list of ints") + + swap_gates = list() + if swap: + swap_registers(swap_gates, qubit_list) + + qft_gates = list() + if inverse: + append_qft_rotations_gates(qft_gates, qubit_list, prefac=-1) + qft_gates = [gate for gate in reversed(qft_gates)] + qft_gates = swap_gates + qft_gates + else: + append_qft_rotations_gates(qft_gates, qubit_list) + qft_gates += swap_gates + + return Circuit(qft_gates, n_qubits=n_qubits) + + +def controlled_pauliwords(qubit_op, control, n_qubits=None): + """Takes a qubit operator and returns controlled-pauliword circuits for each term as a list. + + Args: + qubit_op (QubitOperator): The qubit operator with pauliwords to generate circuits for + control (int): The index of the control qubit + n_qubits (int): When generating each Circuit, create with n_qubits size + + Returns: + list: List of controlled-pauliword Circuit for each pauliword in the qubit_op + """ + pauli_words = qubit_op.terms.items() + + pauliword_circuits = list() + for (pauli_word, _) in pauli_words: + gates = [Gate(name="C"+op, target=index, control=control) for index, op in pauli_word] + pauliword_circuits.append(Circuit(gates, n_qubits=n_qubits)) + return pauliword_circuits + + +def controlled_swap_to_XX_gates(c, n1, n2): + """Equivalent decomposition of controlled swap into 1-qubit gates and XX 2-qubit gate. + + This is useful for IonQ experiments as the native two-qubit gate is the XX Ising coupling. + + Args: + c (int): control qubit + n1 (int): first target qubit + n2 (int): second target qubit + + Returns: + list: List of Gate that applies controlled swap operation + """ + gates = [Gate("RY", target=c, parameter=7*np.pi/2.), + Gate("RZ", target=n1, parameter=7*np.pi/2.), + Gate("XX", target=[n1, n2], parameter=5*np.pi/2.), + Gate("RZ", target=n1, parameter=7*np.pi/4.), + Gate("RZ", target=n2, parameter=3*np.pi/4.), + Gate("RY", target=n1, parameter=np.pi/2.), + Gate("XX", target=[c, n2], parameter=7*np.pi/2.), + Gate("RY", target=n2, parameter=11*np.pi/4), + Gate("XX", target=[n1, n2], parameter=7*np.pi/2.), + Gate("XX", target=[c, n1], parameter=np.pi/4.), + Gate("RZ", target=n2, parameter=np.pi/4), + Gate("XX", target=[c, n2], parameter=5*np.pi/2), + Gate("RY", target=c, parameter=5*np.pi/2), + Gate("RZ", target=n1, parameter=5*np.pi/2), + Gate("RY", target=n2, parameter=7*np.pi/4), + Gate("XX", target=[n1, n2], parameter=7*np.pi/2), + Gate("RY", target=n1, parameter=np.pi/2), + Gate("RZ", target=c, parameter=11*np.pi/4)] + return gates + + +def derangement_circuit(qubit_list, control=None, n_qubits=None, decomp=None): + """Returns the (controlled-)derangement circuit for multiple copies of a state + + Args: + qubit_list (list of list(int)): Each item in the list is a list of qubit registers for each copy. The length of + each list of qubit registers must be the same. + For example [[1, 2], [3, 4]] applies controlled-swaps between equivalent states located on qubits [1, 2] and [3, 4] + control (int): The control register to be measured. + n_qubits (int): The number of qubits in the circuit. + decomp (str): Use the decomposed controlled-swap into 1-qubit gates and a certain 2-qubit gate listed below. + "XX": 2-qubit gate is XX + + Returns: + Circuit: The derangement circuit + """ + if decomp is not None and decomp not in ["XX"]: + raise ValueError(f"{decomp} is not a valid controlled swap decomposition") + + num_copies = len(qubit_list) + if num_copies == 1: + return Circuit(n_qubits=n_qubits) + else: + rho_range = len(qubit_list[0]) + for i in range(1, num_copies): + if len(qubit_list[i]) != rho_range: + raise ValueError("All copies must have the same number of qubits") + gate_list = list() + if control is None: + for copy1, copy2 in combinations(range(num_copies), 2): + for rhoi in range(rho_range): + gate_list += [Gate("SWAP", target=[qubit_list[copy1][rhoi], qubit_list[copy2][rhoi]])] + else: + for copy1, copy2 in combinations(range(num_copies), 2): + for rhoi in range(rho_range): + if decomp == "XX": + gate_list += controlled_swap_to_XX_gates(control, + qubit_list[copy1][rhoi], + qubit_list[copy2][rhoi]) + else: + gate_list += [Gate("CSWAP", + target=[qubit_list[copy1][rhoi], qubit_list[copy2][rhoi]], + control=control)] + + return Circuit(gate_list, n_qubits=n_qubits) diff --git a/tangelo/toolboxes/ansatz_generator/qcc.py b/tangelo/toolboxes/ansatz_generator/qcc.py index c6be5852e..737da5975 100755 --- a/tangelo/toolboxes/ansatz_generator/qcc.py +++ b/tangelo/toolboxes/ansatz_generator/qcc.py @@ -41,7 +41,7 @@ fermion_to_qubit_mapping from tangelo.linq import Circuit from .ansatz import Ansatz -from .ansatz_utils import pauliword_to_circuit +from .ansatz_utils import exp_pauliword_to_gates from ._qubit_mf import init_qmf_from_hf, get_qmf_circuit, purify_qmf_state from ._qubit_cc import construct_dis @@ -208,7 +208,7 @@ def build_circuit(self, var_params=None): pauli_words = sorted(qubit_op.terms.items(), key=lambda x: len(x[0])) pauli_words_gates = [] for i, (pauli_word, coef) in enumerate(pauli_words): - pauli_words_gates += pauliword_to_circuit(pauli_word, coef) + pauli_words_gates += exp_pauliword_to_gates(pauli_word, coef) self.pauli_to_angles_mapping[pauli_word] = i self.qcc_circuit = Circuit(pauli_words_gates) self.circuit = self.qmf_circuit + self.qcc_circuit if self.qmf_circuit.size != 0\ diff --git a/tangelo/toolboxes/ansatz_generator/tests/test_ansatz_util.py b/tangelo/toolboxes/ansatz_generator/tests/test_ansatz_util.py new file mode 100644 index 000000000..10068e235 --- /dev/null +++ b/tangelo/toolboxes/ansatz_generator/tests/test_ansatz_util.py @@ -0,0 +1,301 @@ +import unittest + +from scipy.linalg import expm +import numpy as np +from numpy.linalg import eigh +from openfermion import get_sparse_operator + +from tangelo.linq import Simulator, Circuit, Gate +from tangelo.toolboxes.operators import FermionOperator, QubitOperator +from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping +from tangelo.molecule_library import mol_H4_sto3g +from tangelo.linq.tests.test_simulator import assert_freq_dict_almost_equal +from tangelo.toolboxes.qubit_mappings.statevector_mapping import get_reference_circuit +from tangelo.toolboxes.ansatz_generator.ansatz_utils import trotterize, get_qft_circuit +from tangelo.toolboxes.ansatz_generator.ansatz_utils import controlled_swap_to_XX_gates +from tangelo.toolboxes.ansatz_generator.ansatz_utils import derangement_circuit, controlled_pauliwords + +# Initiate simulators, Use cirq as it has the same ordering for statevectors as openfermion does for Hamiltonians +# This is important when converting the openfermion QubitOperator toarray(), propagating exactly and comparing +# to the statevector output of the simulator. All other simulators will produce the same statevector values but +# in a different order (i.e. msq_first instead of lsq_first) +sim = Simulator(target="cirq") + +fermion_operator = mol_H4_sto3g._get_fermionic_hamiltonian() + + +class ansatz_utils_Test(unittest.TestCase): + + def test_trotterize_fermion_input(self): + """ Verify that the time evolution is correct for different mappings and a fermionic + hamiltonian input + """ + + time = 0.2 + for mapping in ["jw", "bk", "scbk"]: + reference_circuit = get_reference_circuit(n_spinorbitals=mol_H4_sto3g.n_active_sos, + n_electrons=mol_H4_sto3g.n_active_electrons, + mapping=mapping, + up_then_down=True) + _, refwave = sim.simulate(reference_circuit, return_statevector=True) + + qubit_hamiltonian = fermion_to_qubit_mapping(fermion_operator=fermion_operator, + mapping=mapping, + n_spinorbitals=mol_H4_sto3g.n_active_sos, + n_electrons=mol_H4_sto3g.n_active_electrons, + up_then_down=True) + + ham_mat = get_sparse_operator(qubit_hamiltonian).toarray() + evolve_exact = expm(-1j * time * ham_mat) @ refwave + + options = {"up_then_down": True, + "qubit_mapping": mapping, + "n_spinorbitals": mol_H4_sto3g.n_active_sos, + "n_electrons": mol_H4_sto3g.n_active_electrons} + tcircuit, phase = trotterize(fermion_operator, trotter_order=1, n_trotter_steps=1, time=time, + mapping_options=options, return_phase=True) + _, wavefunc = sim.simulate(tcircuit, return_statevector=True, initial_statevector=refwave) + wavefunc *= phase + overlap = np.dot(np.conj(evolve_exact), wavefunc) + self.assertAlmostEqual(overlap, 1.0, delta=1e-3) + + def test_trotterize_qubit_input(self): + """ Verify that the time evolution is correct for different mappings and a qubit_hamiltonian input""" + + time = 0.2 + for mapping in ["jw", "bk", "scbk"]: + reference_circuit = get_reference_circuit(n_spinorbitals=mol_H4_sto3g.n_active_sos, + n_electrons=mol_H4_sto3g.n_active_electrons, + mapping=mapping, + up_then_down=True) + _, refwave = sim.simulate(reference_circuit, return_statevector=True) + + qubit_hamiltonian = fermion_to_qubit_mapping(fermion_operator=fermion_operator, + mapping=mapping, + n_spinorbitals=mol_H4_sto3g.n_active_sos, + n_electrons=mol_H4_sto3g.n_active_electrons, + up_then_down=True) + + ham_mat = get_sparse_operator(qubit_hamiltonian).toarray() + evolve_exact = expm(-1j * time * ham_mat) @ refwave + + tcircuit, phase = trotterize(qubit_hamiltonian, trotter_order=1, n_trotter_steps=1, time=time, + return_phase=True) + _, wavefunc = sim.simulate(tcircuit, return_statevector=True, initial_statevector=refwave) + wavefunc *= phase + overlap = np.dot(np.conj(evolve_exact), wavefunc) + self.assertAlmostEqual(overlap, 1.0, delta=1e-3) + + def test_trotterize_different_order_and_steps(self): + """ Verify that the time evolution is correct for different orders and number of steps + with a qubit_hamiltonian input""" + + time = 0.2 + mapping = "bk" + reference_circuit = get_reference_circuit(n_spinorbitals=mol_H4_sto3g.n_active_sos, + n_electrons=mol_H4_sto3g.n_active_electrons, + mapping=mapping, + up_then_down=True) + _, refwave = sim.simulate(reference_circuit, return_statevector=True) + + qubit_hamiltonian = fermion_to_qubit_mapping(fermion_operator=fermion_operator, + mapping=mapping, + n_spinorbitals=mol_H4_sto3g.n_active_sos, + n_electrons=mol_H4_sto3g.n_active_electrons, + up_then_down=True) + + ham_mat = get_sparse_operator(qubit_hamiltonian).toarray() + evolve_exact = expm(-1j * time * ham_mat) @ refwave + + for trotter_order, n_trotter_steps in [(1, 1), (2, 1), (1, 2)]: + + tcircuit, phase = trotterize(qubit_hamiltonian, time, n_trotter_steps, trotter_order, return_phase=True) + _, wavefunc = sim.simulate(tcircuit, return_statevector=True, initial_statevector=refwave) + wavefunc *= phase + overlap = np.dot(np.conj(evolve_exact), wavefunc) + self.assertAlmostEqual(overlap, 1.0, delta=1e-3) + + def test_trotterize_fermionic_input_different_times(self): + """ Verify that the time evolution is correct for a FermionOperator input with different times + for each term + """ + + mapping = "jw" + # generate Hermitian FermionOperator + fermion_operators = [FermionOperator("0^ 3", 0.5) + FermionOperator("3^ 0", 0.5), + FermionOperator("1^ 2", 0.5) + FermionOperator("2^ 1", 0.5), + FermionOperator("1^ 3", 0.5) + FermionOperator("3^ 1", 0.5)] + + # time is twice as long as each Hermitian Operator has two terms + time = {((0, 1), (3, 0)): 0.1, ((3, 1), (0, 0)): 0.1, ((1, 1), (2, 0)): 0.2, ((2, 1), (1, 0)): 0.2, + ((1, 1), (3, 0)): 0.3, ((3, 1), (1, 0)): 0.3} + + # Build referenc circuit and obtain reference wavefunction + reference_circuit = Circuit([Gate("X", 0), Gate("X", 3)]) + _, refwave = sim.simulate(reference_circuit, return_statevector=True) + + evolve_exact = refwave + total_fermion_operator = FermionOperator() + # evolve each term separately and apply to resulting wavefunction + for i in range(3): + total_fermion_operator += fermion_operators[i] + qubit_hamiltonian = fermion_to_qubit_mapping(fermion_operator=fermion_operators[i], + mapping=mapping) + ham_mat = get_sparse_operator(qubit_hamiltonian, n_qubits=4).toarray() + evolve_exact = expm(-1j * time[next(iter(fermion_operators[i].terms))] * ham_mat) @ evolve_exact + + # Apply trotter-suzuki steps using different times for each term + tcircuit, phase = trotterize(total_fermion_operator, trotter_order=1, n_trotter_steps=1, time=time, return_phase=True) + _, wavefunc = sim.simulate(tcircuit, return_statevector=True, initial_statevector=refwave) + wavefunc *= phase + + overlap = np.dot(np.conj(evolve_exact), wavefunc) + self.assertAlmostEqual(overlap, 1.0, delta=1e-3) + + def test_trotterize_qubit_input_different_times(self): + """ Verify that the time evolution is correct for a QubitOperator input with different times + for each term + """ + + qubit_operator_list = [QubitOperator("X0 Y1", 0.5), QubitOperator("Y1 Z2", 0.5), QubitOperator("Y2 X3", 0.5)] + + time = {((0, 'X'), (1, 'Y')): 0.1, ((1, 'Y'), (2, 'Z')): 0.2, ((2, 'Y'), (3, 'X')): 0.3} + + # Generate initial wavefunction + reference_circuit = Circuit([Gate("X", 0), Gate("X", 3)]) + _, refwave = sim.simulate(reference_circuit, return_statevector=True) + + # Exactly evolve for each time step + evolve_exact = refwave + for i in range(3): + ham_mat = get_sparse_operator(qubit_operator_list[i], n_qubits=4).toarray() + evolve_exact = expm(-1j * time[next(iter(qubit_operator_list[i].terms))] * ham_mat) @ evolve_exact + + # Apply trotter-suzuki with different times for each qubit operator term + total_qubit_operator = QubitOperator() + for qu_op in reversed(qubit_operator_list): + total_qubit_operator += qu_op + tcircuit, phase = trotterize(total_qubit_operator, trotter_order=2, n_trotter_steps=2, time=time, return_phase=True) + _, wavefunc = sim.simulate(tcircuit, return_statevector=True, initial_statevector=refwave) + wavefunc *= phase + overlap = np.dot(np.conj(evolve_exact), wavefunc) + self.assertAlmostEqual(overlap, 1.0, delta=1e-3) + + def test_qft_by_phase_estimation(self): + """Test get_qft_circuit by applying phase-estimation to a 1-qubit operator with eigenvalue -1, i.e. phi=1/2""" + n_qubits = 4 + qubit_list = [2, 1, 0] + # Generate state with eigenvalue -1 of X operator exp(2*pi*i*phi) phi=1/2 + gate_list = [Gate("X", target=n_qubits-1), Gate("H", target=n_qubits-1)] + + # Generate phase-estimation circuit with three registers + pe_circuit = Circuit(gate_list, n_qubits=n_qubits) + qft = get_qft_circuit(qubit_list, n_qubits=n_qubits) + pe_circuit += qft + controlled_unitaries = [] + for i, qubit in enumerate(qubit_list): + for j in range(2**i): + controlled_unitaries += [Gate("CNOT", target=n_qubits-1, control=qubit)] + pe_circuit += Circuit(controlled_unitaries, n_qubits=n_qubits) + iqft = get_qft_circuit(qubit_list, n_qubits=n_qubits, inverse=True) + pe_circuit += iqft + + # simulate starting state frequency is {"0001": 0.5, "0000": 0.5} + freqs, _ = sim.simulate(pe_circuit) + # phase is added to first three qubits with value 100 = 1 * 1/2 + 0 * 1/4 + 0 * 1/8 + # while keeping last qubit unchanged. Therefore, the target frequency dictionary is target_freq_dict + target_freq_dict = {"1000": 0.5, "1001": 0.5} + assert_freq_dict_almost_equal(target_freq_dict, freqs, atol=1.e-7) + + def test_controlled_time_evolution_by_phase_estimation(self): + """ Verify that the time evolution is correct for a QubitOperator input with different times + for each term + """ + + # Generate qubit operator with state 9 having eigenvalue 0.25 + qu_op = (QubitOperator("X0 X1", 0.125) + QubitOperator("Y1 Y2", 0.125) + QubitOperator("Z2 Z3", 0.125) + + QubitOperator("", 0.125)) + + ham_mat = get_sparse_operator(qu_op).toarray() + _, wavefunction = eigh(ham_mat) + + # Append four qubits in the zero state to eigenvector 9 + wave_9 = wavefunction[:, 9] + for i in range(4): + wave_9 = np.kron(wave_9, np.array([1, 0])) + + n_qubits = 8 + + qubit_list = [7, 6, 5, 4] + + qft = get_qft_circuit(qubit_list, n_qubits=n_qubits) + pe_circuit = qft + for i, qubit in enumerate(qubit_list): + u_circuit = trotterize(qu_op, trotter_order=1, n_trotter_steps=10, time=-2*np.pi, control=qubit) + for j in range(2**i): + pe_circuit += u_circuit + iqft = get_qft_circuit(qubit_list, n_qubits=n_qubits, inverse=True) + pe_circuit += iqft + + freqs, _ = sim.simulate(pe_circuit, initial_statevector=wave_9) + + # Trace out first 4 dictionary amplitudes, only care about final 4 indices + trace_freq = dict() + for key, value in freqs.items(): + trace_freq[key[-4:]] = trace_freq.get(key[-4:], 0) + value + + # State 9 has eigenvalue 0.25 so return should be 0100 (0*1/2 + 1*1/4 + 0*1/8 + 0*1/16) + self.assertAlmostEqual(trace_freq["0100"], 1.0, delta=2) + + def test_controlled_swap(self): + cswap_circuits = [Circuit([Gate("CSWAP", target=[1, 2], control=0)]), + Circuit(controlled_swap_to_XX_gates(0, 1, 2))] + + for cswap_circuit in cswap_circuits: + # initialize in "110", returns "101" + init_gates = [Gate("X", target=0), Gate("X", target=1)] + circuit = Circuit(init_gates, n_qubits=3) + cswap_circuit + freqs, _ = sim.simulate(circuit, return_statevector=True) + assert_freq_dict_almost_equal({"101": 1.0}, freqs, atol=1.e-7) + + # initialize in "010" returns "010" + init_gates = [Gate("X", target=1)] + circuit = Circuit(init_gates, n_qubits=3) + cswap_circuit + freqs, _ = sim.simulate(circuit, return_statevector=True) + assert_freq_dict_almost_equal({"010": 1.0}, freqs, atol=1.e-7) + + def test_derangement_circuit_by_estimating_pauli_string(self): + """ Verify that tr(rho^3 pa) for a pauliword pa is correct. + Uses the exponential error suppression circuit + """ + + qu_op = QubitOperator("X0 Y1", 0.125) + QubitOperator("Y1 Y2", 0.125) + QubitOperator("Z2 Z3", 0.125) + pa = QubitOperator("X0 X1 X2 X3", 1) + + ham_mat = get_sparse_operator(qu_op).toarray() + _, wavefunction = eigh(ham_mat) + pamat = get_sparse_operator(pa).toarray() + + mixed_wave = np.sqrt(3)/2*wavefunction[:, -1] + 1/2*wavefunction[:, 0] + mixed_wave_3 = np.kron(np.kron(mixed_wave, mixed_wave), mixed_wave) + full_start_vec = np.kron(mixed_wave_3, np.array([1, 0])) + rho = np.outer(mixed_wave, mixed_wave) + rho3 = rho @ rho @ rho + exact = np.trace(rho3 @ pamat) + + n_qubits = 13 + + qubit_list = [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]] + rho3_pa_circuit = Circuit([Gate("H", target=12)], n_qubits=n_qubits) + derange_circuit = derangement_circuit(qubit_list, control=12, n_qubits=n_qubits) + cpas = controlled_pauliwords(pa, control=12, n_qubits=n_qubits) + rho3_pa_circuit += derange_circuit + cpas[0] + rho3_pa_circuit += Circuit([Gate("H", target=12)], n_qubits=n_qubits) + + exp_op = QubitOperator("Z12", 1) + measured = sim.get_expectation_value(exp_op, rho3_pa_circuit, initial_statevector=full_start_vec) + self.assertAlmostEqual(measured, exact, places=6) + + +if __name__ == "__main__": + unittest.main() diff --git a/tangelo/toolboxes/ansatz_generator/uccsd.py b/tangelo/toolboxes/ansatz_generator/uccsd.py index 8e5c26d8c..58c604943 100644 --- a/tangelo/toolboxes/ansatz_generator/uccsd.py +++ b/tangelo/toolboxes/ansatz_generator/uccsd.py @@ -36,7 +36,7 @@ from tangelo.linq import Circuit from .ansatz import Ansatz -from .ansatz_utils import pauliword_to_circuit +from .ansatz_utils import exp_pauliword_to_gates from ._unitary_cc_openshell import uccsd_openshell_paramsize, uccsd_openshell_generator from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping from tangelo.toolboxes.qubit_mappings.statevector_mapping import get_reference_circuit @@ -166,7 +166,7 @@ def build_circuit(self, var_params=None): pauli_words_gates = [] self.pauli_to_angles_mapping = dict() for i, (pauli_word, coef) in enumerate(pauli_words): - pauli_words_gates += pauliword_to_circuit(pauli_word, coef) + pauli_words_gates += exp_pauliword_to_gates(pauli_word, coef) self.pauli_to_angles_mapping[pauli_word] = i uccsd_circuit = Circuit(pauli_words_gates) diff --git a/tangelo/toolboxes/ansatz_generator/upccgsd.py b/tangelo/toolboxes/ansatz_generator/upccgsd.py index b78093777..c4c2879b6 100644 --- a/tangelo/toolboxes/ansatz_generator/upccgsd.py +++ b/tangelo/toolboxes/ansatz_generator/upccgsd.py @@ -29,7 +29,7 @@ from tangelo.linq import Circuit from .ansatz import Ansatz -from .ansatz_utils import pauliword_to_circuit +from .ansatz_utils import exp_pauliword_to_gates from ._unitary_cc_paired import get_upccgsd from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping from tangelo.toolboxes.qubit_mappings.statevector_mapping import get_reference_circuit @@ -156,7 +156,7 @@ def build_circuit(self, var_params=None): # Obtain quantum circuit through trivial trotterization of the qubit operator for each current_k for i, (pauli_word, coef) in enumerate(pauli_words): - pauli_words_gates += pauliword_to_circuit(pauli_word, coef) + pauli_words_gates += exp_pauliword_to_gates(pauli_word, coef) self.pauli_to_angles_mapping[current_k][pauli_word] = i + sum_prev_qubit_terms[current_k] sum_prev_qubit_terms[current_k + 1] = len(qubit_op.terms.items()) From 2c88004e5ebd65f4e9fbf6be5ab852a41143da29 Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Mon, 3 Jan 2022 12:03:39 +0100 Subject: [PATCH 39/68] test for badge --- tangelo/linq/tests/test_gates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tangelo/linq/tests/test_gates.py b/tangelo/linq/tests/test_gates.py index 934e91c52..e18ced917 100644 --- a/tangelo/linq/tests/test_gates.py +++ b/tangelo/linq/tests/test_gates.py @@ -81,7 +81,7 @@ def test_gate_equality(self): g2 = Gate("CPOTATO", target=2, control=0, parameter="", is_variational=True) g3 = Gate("CPOTATO", target=2, control=0, parameter=0, is_variational=True) - self.assertTrue(g1 == g3) + self.assertTrue(g1 != g3) self.assertTrue(g1 != g2) From ffee436a44d21c09a31c7e1102ea20e990688879 Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Mon, 3 Jan 2022 13:18:17 +0100 Subject: [PATCH 40/68] test for badge --- tangelo/linq/tests/test_gates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tangelo/linq/tests/test_gates.py b/tangelo/linq/tests/test_gates.py index e18ced917..934e91c52 100644 --- a/tangelo/linq/tests/test_gates.py +++ b/tangelo/linq/tests/test_gates.py @@ -81,7 +81,7 @@ def test_gate_equality(self): g2 = Gate("CPOTATO", target=2, control=0, parameter="", is_variational=True) g3 = Gate("CPOTATO", target=2, control=0, parameter=0, is_variational=True) - self.assertTrue(g1 != g3) + self.assertTrue(g1 == g3) self.assertTrue(g1 != g2) From f561009b3c9bc4097b3f265715186d2fd642ed32 Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Mon, 3 Jan 2022 14:12:23 +0100 Subject: [PATCH 41/68] Update README.rst --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index edd8ef12c..12963d42b 100644 --- a/README.rst +++ b/README.rst @@ -4,11 +4,13 @@ Tangelo overview |maintainer| |licence| |build| +|dev_branch| .. |maintainer| image:: https://img.shields.io/badge/Maintainer-GoodChemistry-blue .. |licence| image:: https://img.shields.io/badge/License-Apache_2.0-green :target: https://github.com/quantumsimulation/QEMIST_qSDK/blob/main/README.rst .. |build| image:: https://github.com/quantumsimulation/QEMIST_qSDK/actions/workflows/continuous_integration.yml/badge.svg +.. |dev_branch| image:: https://img.shields.io/badge/DevBranch-staging_0.3.0-yellow Welcome ! From c4c91908e34b3c817fbe63d5467a11e44cdc169a Mon Sep 17 00:00:00 2001 From: AlexandreF-1qbit <76115575+AlexandreF-1qbit@users.noreply.github.com> Date: Tue, 4 Jan 2022 06:09:03 -0500 Subject: [PATCH 42/68] Always run backend installation (#104) * Always run backend installation (tested) --- .github/workflows/github_actions_automated_testing.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/github_actions_automated_testing.yml b/.github/workflows/github_actions_automated_testing.yml index 3c25d2894..ceade1f28 100755 --- a/.github/workflows/github_actions_automated_testing.yml +++ b/.github/workflows/github_actions_automated_testing.yml @@ -16,13 +16,13 @@ jobs: uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - + - name: Install pip, wheel, pytest, jupyter run: | python -m pip install --upgrade pip pip install wheel pip install pytest - pip install pytest-cov + pip install pytest-cov pip install jupyter - name: Install pycodestyle @@ -42,6 +42,7 @@ jobs: pip install amazon-braket-sdk pip install cirq==0.12.0 pip install projectq + if: always() - name: Install Microsoft qsharp/qdk run: | @@ -53,6 +54,7 @@ jobs: dotnet tool install -g Microsoft.Quantum.IQSharp $(which dotnet-iqsharp) install --user pip install qsharp + if: always() - name: tangelo install run: | From 2649ac471975212b298d78136f9d485a3e809cef Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Wed, 5 Jan 2022 17:20:29 +0100 Subject: [PATCH 43/68] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index b05faea30..fa1de06ff 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,5 @@ Tangelo overview -============= +================ Welcome ! From 34124cbcb3dbebe8756b24a2ef6f153459188648 Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Wed, 5 Jan 2022 18:57:23 +0100 Subject: [PATCH 44/68] Progress in contribution file. --- CONTRIBUTIONS.rst | 120 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 CONTRIBUTIONS.rst diff --git a/CONTRIBUTIONS.rst b/CONTRIBUTIONS.rst new file mode 100644 index 000000000..6bcdd2663 --- /dev/null +++ b/CONTRIBUTIONS.rst @@ -0,0 +1,120 @@ +Contributions guidelines +======================== + +Thank you very much for considering contributing to this project; we’d love to have you on board ! Do not feel intimidated by the guidelines and processes we describe in this document: we are here to assist you and help you take things to the finish line. We do not expect you to be an expert in software development or to get everything right on the first attempt: don’t hesitate to open an issue or a pull request, or simply contact us. + +Contributors have various backgounds and experience, from high schoolers to fully fledged quantum scientists or chemists, and there are many ways you can contribute to this project. You can of course open a pull request and extend our codebase, but opening an issue to suggest a new feature, report a bug, improve our documentation or make a Python notebook are just as valuable. + +By joining the Tangelo community, you gain the opportunity to contribute to a collaborative project, in order to advance the field of quantum computing and further develop your own skills. + +This package is under licence `Apache 2.0 `_. + + +Pull request and code review process +------------------------------------ + +All submissions to the Github repository are subject to review by qualified project members. This is done through `Github’s Pull Request process `_. + +We recommend you fork the `main Tangelo repo `_, create and work on your development branch on this fork and then create a Pull Request (PR) to the main tangelo repo. + + +**1. Set up your fork** + +Go to the `main Tangelo repo `_ and click the Fork button in the upper right corner of the screen. +This creates a new Github repo ``https://github.com/USERNAME/tangelo`` where ``USERNAME`` is your Github ID. + +In your terminal, clone the repo on your local machine, and move into the newly created directory: + +.. code-block:: shell + + git clone https://github.com/quantumsimulation/QEMIST_qSDK.git + cd QEMISK_qSDK + +From the perspective of your local clone, your fork is called ``origin`` remote. +Let's synchronize your fork with the main Tangelo repo by adding the latter as the upstream remote. Then update your local main branch: + +.. code-block:: shell +git remote add upstream https://github.com/quantumsimulation/QEMIST_qSDK.git + +git fetch upstream +git checkout main +git merge upstream/main + + +**2. Work on your own developments** + +Create your development branch, based on the `main` branch (or the current development branch listed on the `DevBranch badge <./README.rst>`_) + +.. code-block:: shell + + git checkout main -b your_branch_name + +where ``your_branch_name`` is the name of your own development branch, preferably related to what you will be working on. +Let's assume you've made some changes and committed them with ``git commit``, and that you'd like to push them to your fork (which is referred to as "origin"): + +.. code-block:: shell + + git push origin new_branch_name + + +**3. The Pull Request (PR)** + +Now when you go to https://github.com/quantumsimulation/QEMIST_qSDK, you should be able to create a pull request from the branch on your fork to a branch on the main Tangelo repo. +Give your pull request a name and briefly describe what the purpose is and include a reference to the associated issue if there's one. +Several Tangelo users will receive a notification, and will review your code and leave comments in the PR. You can reply to these comments, or simply apply the recommended changes locally, and then commit and push them like above: it automatically updates your PR. +If there are conflicts, you can solve them locally and push, or directly through Github. + +Getting your code reviewed can feel intimidating, but remember it's just part of a standard process: everyone has to go through it (even the main developers) and it is uncommon for PRs to be approved without changes or questions first. +We suggest you have a look at how other files of this project (source code, tests, docs...) are written and follow the same format fom the start: this way most of the work is done. +We require that you write tests for your code, as well the docstrings for it. Don't worry: we're here to help and there are plenty examples in the repo. +We usually follow the `PEP8 guidelines `_ for our code. If you're using an IDE (Pycharm, etc), it may automatically tell you where your code is not following PEP8 and should be able to automatically reformat your code too. + +Every time you open a PR or push more code into an open one, several automated processes are launched and can be monitored in Github. We discuss them in the section below. + + +## Code Testing Standards + +When a pull request is created or updated, various automatic checks will +run to ensure that the change won't break Cirq and meets our coding standards. + +Cirq contains a continuous integration tool to verify testing. See our +[development page](docs/dev/development.md) on how to run the continuous +integration checks locally. + +Please be aware of the following code standards that will be applied to any +new changes. + +- **Tests**. +Existing tests must continue to pass (or be updated) when new changes are +introduced. We use [pytest](https://docs.pytest.org/en/latest/) to run our +tests. +- **Coverage**. +Code should be covered by tests. +We use [pytest-cov](https://pytest-cov.readthedocs.io/en/latest/) to compute +coverage, and custom tooling to filter down the output to only include new or +changed code. We don't require 100% coverage, but any uncovered code must +be annotated with `# coverage: ignore`. To ignore coverage of a single line, +place `# coverage: ignore` at the end of the line. To ignore coverage for +an entire block, start the block with a `# coverage: ignore` comment on its +own line. +- **Lint**. +Code should meet common style standards for python and be free of error-prone +constructs. We use [pylint](https://www.pylint.org/) to check for lint. +To see which lint checks we enforce, see the +[dev_tools/conf/.pylintrc](dev_tools/conf/.pylintrc) file. When pylint produces +a false positive, it can be squashed with annotations like +`# pylint: disable=unused-import`. +- **Types**. +Code should have [type annotations](https://www.python.org/dev/peps/pep-0484/). +We use [mypy](http://mypy-lang.org/) to check that type annotations are correct. +When type checking produces a false positive, it can be ignored with +annotations like `# type: ignore`. + +## Request For Comment Process for New Major Features + +For larger contributions that will benefit from design reviews, please use the +[Request for Comment](docs/dev/rfc_process.md) process. + +## Developing notebooks + +Please refer to our [notebooks guide](docs/dev/notebooks.md) on how to develop iPython notebooks for documentation. From 21ca2529db9d19d720f957a8d57aedec770ae69e Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Wed, 5 Jan 2022 22:52:40 +0100 Subject: [PATCH 45/68] Update CONTRIBUTIONS.rst --- CONTRIBUTIONS.rst | 93 ++++++++++++++++++++++------------------------- 1 file changed, 43 insertions(+), 50 deletions(-) diff --git a/CONTRIBUTIONS.rst b/CONTRIBUTIONS.rst index 6bcdd2663..25dc5abd3 100644 --- a/CONTRIBUTIONS.rst +++ b/CONTRIBUTIONS.rst @@ -10,6 +10,13 @@ By joining the Tangelo community, you gain the opportunity to contribute to a co This package is under licence `Apache 2.0 `_. +Feature requests, bug reports +----------------------------- + +Have a look at the issue tab, and complete the adequate issue template if needed: there's one for feature request, bug reports, and more. If it turns out the issue ticket you wanted to bring up already exist, please consider leaving a thumbs up or participate in the conversation to help us prioritize or move things forward. It's important to know what matters to users, to take our collaborative project in the right direction. + + + Pull request and code review process ------------------------------------ @@ -65,56 +72,42 @@ Several Tangelo users will receive a notification, and will review your code and If there are conflicts, you can solve them locally and push, or directly through Github. Getting your code reviewed can feel intimidating, but remember it's just part of a standard process: everyone has to go through it (even the main developers) and it is uncommon for PRs to be approved without changes or questions first. -We suggest you have a look at how other files of this project (source code, tests, docs...) are written and follow the same format fom the start: this way most of the work is done. +We suggest you have a look at how other files of this project (source code, tests, docs...) are written and follow the same format fom the start: this way most of the work is done. + We require that you write tests for your code, as well the docstrings for it. Don't worry: we're here to help and there are plenty examples in the repo. We usually follow the `PEP8 guidelines `_ for our code. If you're using an IDE (Pycharm, etc), it may automatically tell you where your code is not following PEP8 and should be able to automatically reformat your code too. -Every time you open a PR or push more code into an open one, several automated processes are launched and can be monitored in Github. We discuss them in the section below. - - -## Code Testing Standards - -When a pull request is created or updated, various automatic checks will -run to ensure that the change won't break Cirq and meets our coding standards. - -Cirq contains a continuous integration tool to verify testing. See our -[development page](docs/dev/development.md) on how to run the continuous -integration checks locally. - -Please be aware of the following code standards that will be applied to any -new changes. - -- **Tests**. -Existing tests must continue to pass (or be updated) when new changes are -introduced. We use [pytest](https://docs.pytest.org/en/latest/) to run our -tests. -- **Coverage**. -Code should be covered by tests. -We use [pytest-cov](https://pytest-cov.readthedocs.io/en/latest/) to compute -coverage, and custom tooling to filter down the output to only include new or -changed code. We don't require 100% coverage, but any uncovered code must -be annotated with `# coverage: ignore`. To ignore coverage of a single line, -place `# coverage: ignore` at the end of the line. To ignore coverage for -an entire block, start the block with a `# coverage: ignore` comment on its -own line. -- **Lint**. -Code should meet common style standards for python and be free of error-prone -constructs. We use [pylint](https://www.pylint.org/) to check for lint. -To see which lint checks we enforce, see the -[dev_tools/conf/.pylintrc](dev_tools/conf/.pylintrc) file. When pylint produces -a false positive, it can be squashed with annotations like -`# pylint: disable=unused-import`. -- **Types**. -Code should have [type annotations](https://www.python.org/dev/peps/pep-0484/). -We use [mypy](http://mypy-lang.org/) to check that type annotations are correct. -When type checking produces a false positive, it can be ignored with -annotations like `# type: ignore`. - -## Request For Comment Process for New Major Features - -For larger contributions that will benefit from design reviews, please use the -[Request for Comment](docs/dev/rfc_process.md) process. - -## Developing notebooks - -Please refer to our [notebooks guide](docs/dev/notebooks.md) on how to develop iPython notebooks for documentation. +Every time you open a PR or push more code into an open one, several automated processes are launched and can be monitored on Github, and must be successful. We elaborate on them in the section below. + + +Continuous integration +======================= + +When a pull request is created or updated, several automated processes are launched. You will find most of them in the "checks" tab of your pull request, and can look into the details. These processes check for a few things: + +- **Build** + +This step attempts to build and install both Tangelo and its dependencies using your branch. It is necessary for this to succeed in order for most other checks to run. + +- **Tests** +New changes should not break existing features: that's why we're running all the existing tests, on top of your new tests. If something fails, it may be a consequence of your changes, and we should find out what's going on. We use [pytest](https://docs.pytest.org/en/latest/) to run our tests. + +You can run tests locally with unittest; just move to the `tangelo` subfolder of the repo, which contains the source code, and type: + +.. code-block:: shell + + python -m unittest + +This will run all the tests found in the subdirectories, using your local environment (which may not exactly be the one used in the automated tests). +We also have tests that run a few important example notebooks that can execute quickly. + +- **Linting / code style** + +A way to check that your code complies with our style guidelines, based on PEP8. +We rely on a tool called pycodestyle. If you want to know exactly what this linting enforces and ignores, you can refer to this `file <./dev_tools/pycodestyle>`_ and `pycodestyle's documentation `_. + + +Developing notebooks +==================== + +Jupyter notebooks are great ! If you feel like making a notebook to show how to do something cool with Tangelo, don't hesitate to reach out. It counts as code, so it will go through the standard PR process and will need to meet a few requirements. The developer team has made several notebooks you can look at, for inspiration. From a4e1c8d9e8fa84146af4db990b6c8f84614a8f69 Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Wed, 5 Jan 2022 23:43:02 +0100 Subject: [PATCH 46/68] Contribution document done. --- CONTRIBUTIONS.rst | 68 +++++++++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/CONTRIBUTIONS.rst b/CONTRIBUTIONS.rst index 25dc5abd3..f50bdfcfd 100644 --- a/CONTRIBUTIONS.rst +++ b/CONTRIBUTIONS.rst @@ -1,28 +1,28 @@ Contributions guidelines ======================== -Thank you very much for considering contributing to this project; we’d love to have you on board ! Do not feel intimidated by the guidelines and processes we describe in this document: we are here to assist you and help you take things to the finish line. We do not expect you to be an expert in software development or to get everything right on the first attempt: don’t hesitate to open an issue or a pull request, or simply contact us. +Thank you very much for considering contributing to this project; we’d love to have you on board ! -Contributors have various backgounds and experience, from high schoolers to fully fledged quantum scientists or chemists, and there are many ways you can contribute to this project. You can of course open a pull request and extend our codebase, but opening an issue to suggest a new feature, report a bug, improve our documentation or make a Python notebook are just as valuable. +Do not feel intimidated by the guidelines and processes we describe in this document: we are here to assist you and help you take things to the finish line. We do not expect you to be an expert in software development or to get everything right on the first attempt: don’t hesitate to open an issue or a pull request, or simply contact us. + +Contributors have various backgounds and experience, from high schoolers to fully fledged quantum scientists or chemists, and there are many ways you can contribute to this project. You can of course open a pull request (PR) and extend our codebase, but opening an issue to suggest a new feature, report a bug, improve our documentation or make a Jupyter notebook is just as valuable. By joining the Tangelo community, you gain the opportunity to contribute to a collaborative project, in order to advance the field of quantum computing and further develop your own skills. -This package is under licence `Apache 2.0 `_. +Tangelo is under licence `Apache 2.0 `_. Feature requests, bug reports ----------------------------- -Have a look at the issue tab, and complete the adequate issue template if needed: there's one for feature request, bug reports, and more. If it turns out the issue ticket you wanted to bring up already exist, please consider leaving a thumbs up or participate in the conversation to help us prioritize or move things forward. It's important to know what matters to users, to take our collaborative project in the right direction. +Have a look at the issue tab, and complete the adequate issue template if needed: there's one for feature request, bug reports, and more. If it turns out the issue ticket you wanted to bring up already exists, please consider leaving a thumbs up or participate in the conversation to help us prioritize or move things forward. It's important to know what matters to users, to take our collaborative project in the right direction. Pull request and code review process ------------------------------------ -All submissions to the Github repository are subject to review by qualified project members. This is done through `Github’s Pull Request process `_. - -We recommend you fork the `main Tangelo repo `_, create and work on your development branch on this fork and then create a Pull Request (PR) to the main tangelo repo. +All submissions to the Github repository are subject to review by qualified project members. This is done through `Github’s Pull Request process `_. We recommend you fork the `main Tangelo repo `_, create and work on your development branch on this fork and then create a Pull Request (PR) to the main tangelo repo. **1. Set up your fork** @@ -37,20 +37,21 @@ In your terminal, clone the repo on your local machine, and move into the newly git clone https://github.com/quantumsimulation/QEMIST_qSDK.git cd QEMISK_qSDK -From the perspective of your local clone, your fork is called ``origin`` remote. -Let's synchronize your fork with the main Tangelo repo by adding the latter as the upstream remote. Then update your local main branch: +From the perspective of your local clone, your fork is called the ``origin`` remote. +Let's synchronize your fork with the main Tangelo repo by adding the latter as the upstream remote, and then update your local ``main`` branch: .. code-block:: shell -git remote add upstream https://github.com/quantumsimulation/QEMIST_qSDK.git -git fetch upstream -git checkout main -git merge upstream/main + git remote add upstream https://github.com/quantumsimulation/QEMIST_qSDK.git + + git fetch upstream + git checkout main + git merge upstream/main **2. Work on your own developments** -Create your development branch, based on the `main` branch (or the current development branch listed on the `DevBranch badge <./README.rst>`_) +Create your development branch, based on the ``main`` branch (or the current development branch listed on the `DevBranch badge <./README.rst>`_) .. code-block:: shell @@ -66,18 +67,16 @@ Let's assume you've made some changes and committed them with ``git commit``, an **3. The Pull Request (PR)** -Now when you go to https://github.com/quantumsimulation/QEMIST_qSDK, you should be able to create a pull request from the branch on your fork to a branch on the main Tangelo repo. -Give your pull request a name and briefly describe what the purpose is and include a reference to the associated issue if there's one. +Now when you go to https://github.com/quantumsimulation/QEMIST_qSDK, you should be able to create a pull request from the branch on your fork to a branch on the main Tangelo repo. Give your pull request a name and briefly describe what the purpose is and include a reference to the associated issue if there's one. Several Tangelo users will receive a notification, and will review your code and leave comments in the PR. You can reply to these comments, or simply apply the recommended changes locally, and then commit and push them like above: it automatically updates your PR. If there are conflicts, you can solve them locally and push, or directly through Github. -Getting your code reviewed can feel intimidating, but remember it's just part of a standard process: everyone has to go through it (even the main developers) and it is uncommon for PRs to be approved without changes or questions first. -We suggest you have a look at how other files of this project (source code, tests, docs...) are written and follow the same format fom the start: this way most of the work is done. +Getting your code reviewed can feel intimidating, but remember it's just part of a standard process: everyone has to go through it (even the main developers) and it is actually uncommon for PRs to be approved without changes or questions first. We suggest you have a look at how other files of this project (source code, tests, docs...) are written, and follow the same format fom the start to avoid having to make a lot of changes to your code later on. -We require that you write tests for your code, as well the docstrings for it. Don't worry: we're here to help and there are plenty examples in the repo. -We usually follow the `PEP8 guidelines `_ for our code. If you're using an IDE (Pycharm, etc), it may automatically tell you where your code is not following PEP8 and should be able to automatically reformat your code too. +We require that you write tests for your code, as well as the docstrings for it. Don't worry: we're here to help and there are plenty examples in the repo. +We usually follow the `PEP8 guidelines `_ for our code. If you're using an IDE (Pycharm, etc), it may automatically highlight the part of your code that is not following PEP8, and should be able to automatically reformat your code too. -Every time you open a PR or push more code into an open one, several automated processes are launched and can be monitored on Github, and must be successful. We elaborate on them in the section below. +Every time you open a PR or push more code into an open one, several automated processes are launched and can be monitored on Github: we need them to be successful. We elaborate on them in the section below. Continuous integration @@ -85,29 +84,30 @@ Continuous integration When a pull request is created or updated, several automated processes are launched. You will find most of them in the "checks" tab of your pull request, and can look into the details. These processes check for a few things: -- **Build** +**Build** -This step attempts to build and install both Tangelo and its dependencies using your branch. It is necessary for this to succeed in order for most other checks to run. + This step attempts to build and install both Tangelo and its dependencies using your branch. It is necessary for this to succeed in order for most other checks to run. -- **Tests** -New changes should not break existing features: that's why we're running all the existing tests, on top of your new tests. If something fails, it may be a consequence of your changes, and we should find out what's going on. We use [pytest](https://docs.pytest.org/en/latest/) to run our tests. +**Tests** -You can run tests locally with unittest; just move to the `tangelo` subfolder of the repo, which contains the source code, and type: + New changes should not break existing features: that's why we're running all the existing tests, on top of your new tests. If something fails, it may be a consequence of your changes, and we should find out what's going on. We use [pytest](https://docs.pytest.org/en/latest/) to run our tests. -.. code-block:: shell + You can run tests locally with unittest; just move to the `tangelo` subfolder of the repo, which contains the source code, and type: + + .. code-block:: shell - python -m unittest + python -m unittest -This will run all the tests found in the subdirectories, using your local environment (which may not exactly be the one used in the automated tests). -We also have tests that run a few important example notebooks that can execute quickly. + This will run all the tests found in the subdirectories, using your local environment (which may not exactly be the one used in the automated tests). + We also have tests that run a few important example notebooks that can execute quickly. -- **Linting / code style** +**Linting / code style** -A way to check that your code complies with our style guidelines, based on PEP8. -We rely on a tool called pycodestyle. If you want to know exactly what this linting enforces and ignores, you can refer to this `file <./dev_tools/pycodestyle>`_ and `pycodestyle's documentation `_. + A way to check that your code complies with our style guidelines, based on PEP8. + We rely on a tool called pycodestyle. If you want to know exactly what this linting enforces and ignores, you can refer to this `file <./dev_tools/pycodestyle>`_ and `pycodestyle's documentation `_. Developing notebooks ==================== -Jupyter notebooks are great ! If you feel like making a notebook to show how to do something cool with Tangelo, don't hesitate to reach out. It counts as code, so it will go through the standard PR process and will need to meet a few requirements. The developer team has made several notebooks you can look at, for inspiration. +Jupyter notebooks are great ! If you feel like making a notebook to show how to do something cool with Tangelo and educate others, don't hesitate to reach out. It counts as code, so it will go through the standard PR process and will need to meet a few requirements. The developer team has made several notebooks you can look at, for inspiration. From 989b421bb520fc42ad9674fab0ea4ef0a20f720e Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Wed, 5 Jan 2022 23:46:45 +0100 Subject: [PATCH 47/68] Update CONTRIBUTIONS.rst --- CONTRIBUTIONS.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTIONS.rst b/CONTRIBUTIONS.rst index f50bdfcfd..e8f25993f 100644 --- a/CONTRIBUTIONS.rst +++ b/CONTRIBUTIONS.rst @@ -15,7 +15,7 @@ Tangelo is under licence `Apache 2.0 `_ to run our tests. You can run tests locally with unittest; just move to the `tangelo` subfolder of the repo, which contains the source code, and type: From 2bc8d73ec459d146d81b5b7676aa0738967bed51 Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Wed, 5 Jan 2022 23:54:05 +0100 Subject: [PATCH 48/68] Update README.rst --- README.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.rst b/README.rst index 12963d42b..be42213df 100644 --- a/README.rst +++ b/README.rst @@ -94,6 +94,12 @@ find and run all tests (assuming you are in the ``tangelo`` subfolder that conta python -m unittest + +Contributions +------------- + +Please have a look at the `contributions <./CONTRIBUTIONS.rst>`_ file. + Citations --------- From e7155137ec6bd85e8ebe295ee35428341a5d95a9 Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Thu, 6 Jan 2022 00:43:01 +0100 Subject: [PATCH 49/68] Update CONTRIBUTIONS.rst --- CONTRIBUTIONS.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTIONS.rst b/CONTRIBUTIONS.rst index e8f25993f..b93e3009d 100644 --- a/CONTRIBUTIONS.rst +++ b/CONTRIBUTIONS.rst @@ -7,7 +7,7 @@ Do not feel intimidated by the guidelines and processes we describe in this docu Contributors have various backgounds and experience, from high schoolers to fully fledged quantum scientists or chemists, and there are many ways you can contribute to this project. You can of course open a pull request (PR) and extend our codebase, but opening an issue to suggest a new feature, report a bug, improve our documentation or make a Jupyter notebook is just as valuable. -By joining the Tangelo community, you gain the opportunity to contribute to a collaborative project, in order to advance the field of quantum computing and further develop your own skills. +By joining the Tangelo community and sharing your ideas and developments, you are creating an opportunity for us to learn and grow together, and take ideas to the finish line and beyond. Tangelo is under licence `Apache 2.0 `_. From f6187c853052a7a2f33dbeccf6b20a60bd72718e Mon Sep 17 00:00:00 2001 From: AlexandreF-1qbit <76115575+AlexandreF-1qbit@users.noreply.github.com> Date: Thu, 6 Jan 2022 08:53:38 -0500 Subject: [PATCH 50/68] Classical shadows (#103) * Foundation for CS and randomized CS Co-authored-by: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> --- .../measurements/classical_shadows.py | 332 ++++++++++++++++++ .../tests/test_classical_shadows.py | 107 ++++++ 2 files changed, 439 insertions(+) create mode 100644 tangelo/toolboxes/measurements/classical_shadows.py create mode 100644 tangelo/toolboxes/measurements/tests/test_classical_shadows.py diff --git a/tangelo/toolboxes/measurements/classical_shadows.py b/tangelo/toolboxes/measurements/classical_shadows.py new file mode 100644 index 000000000..9e4fc2cbc --- /dev/null +++ b/tangelo/toolboxes/measurements/classical_shadows.py @@ -0,0 +1,332 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This file provides an API enabling the use of classical shadows. The original +idea is described in H.Y. Huang, R. Kueng, and J. Preskill, Nature Physics 16, +1050 (2020). +""" + +import abc +import random +import warnings + +import numpy as np + +from tangelo.linq.circuit import Circuit +from tangelo.linq.helpers.circuits.measurement_basis import measurement_basis_gates, pauli_string_to_of + + +# State |0> or |1>. +zero_state = np.array([1, 0]) +one_state = np.array([0, 1]) + +# Pauli matrices. +I = np.array([[1, 0], [0, 1]]) +X = np.array([[0, 1], [1, 0]]) +Y = np.array([[0, -1j], [1j, 0]], dtype=complex) +Z = np.array([[1, 0], [0, -1]]) +matrices = {"X": X, "Y": Y, "Z": Z} + +# Traces of each Pauli matrices. +traces = {pauli: np.trace(matrix) for pauli, matrix in matrices.items()} + +# Reverse channels to undo single Pauli rotations. +S_T = np.array([[1, 0], [0, -1j]], dtype=complex) +H = np.array([[1, 1], [1, -1]]) / np.sqrt(2) +I = np.array([[1, 0], [0, 1]]) +rotations = {"X": H, "Y": H @ S_T, "Z": I} + + +class ClassicalShadow(abc.ABC): + """Abstract class for the classical shadows implementation. Classical + shadows is a mean to characterize a quantum state (within an error treshold) + with the fewest measurement possible. + """ + + def __init__(self, circuit, bitstrings=None, unitaries=None): + """Default constructor for the ClassicalShadow object. This class is + the parent class for the different classical shadows flavors. The object + is defined by the bistrings and unitaries used in the process. Abstract + methods are defined to take into account the procedure to inverse the + channel. + + Args: + bistrings (list of str): Representation of the outcomes for all + snapshots. E.g. ["11011", "10000", ...]. + unitaries (list of str): Representation of the unitary for every + snapshot, used to reverse the channel. + """ + + self.circuit = circuit + self.bitstrings = list() if bitstrings is None else bitstrings + self.unitaries = list() if unitaries is None else unitaries + + # If the state has been estimated, it is stored into this attribute. + self.state_estimate = None + + @property + def n_qubits(self): + """Returns the number of qubits the shadow represents.""" + return self.circuit.width + + @property + def size(self): + """Number of shots used to make the shadow.""" + return len(self.bitstrings) + + @property + def unique_unitaries(self): + """Returns the list of unique unitaries.""" + return list(set(self.unitaries)) + + def __len__(self): + """Same as the shadow size.""" + return self.size + + def append(self, bitstring, unitary): + """Append method to merge new snapshots to an existing shadow. + + Args: + bistring (str or list of str): Representation of outcomes. + unitary (str or list of str): Relevant unitary for those outcomes. + """ + if isinstance(bitstring, list) and isinstance(unitary, list): + assert len(bitstring) == len(unitary) + self.bitstrings += bitstring + self.unitaries += unitary + elif isinstance(bitstring, str) and isinstance(unitary, str): + self.bitstrings.append(bitstring) + self.unitaries.append(unitary) + else: + raise ValueError("bistring and unitary arguments must be consistent strings or list of strings.") + + def get_observable(self, qubit_op, *args, **kwargs): + """Getting an estimated observable value for a qubit operator from the + classical shadow. This function loops through all terms and calls, for + each of them, the get_term_observable method defined in the child class. + Other arguments (args, kwargs) can be passed to the method. + + Args: + qubit_op (QubitOperator): Operator to estimate. + """ + observable = 0. + for term, coeff in qubit_op.terms.items(): + observable += self.get_term_observable(term, coeff, *args, **kwargs) + + return observable + + def simulate(self, backend, initial_statevector=None): + """Simulate, using a predefined backend, a shadow from a circuit or a + statevector. + + Args: + backend (Simulator): Backend for the simulation of a shadow. + initial_statevector(list/array) : A valid statevector in the format + supported by the target backend. + """ + + if not self.unitaries: + raise ValueError(f"The build method of {self.__class__.__name__} must be called before simulation.") + + if backend.n_shots != 1: + warnings.warn(f"Changing number of shots to 1 for the backend (classical shadows).") + backend.n_shots = 1 + + # Different behavior if circuit or initial_statevector is defined. + one_shot_circuit_template = self.circuit if self.circuit is not None else Circuit(n_qubits=self.n_qubits) + + for basis_circuit in self.get_basis_circuits(only_unique=False): + one_shot_circuit = one_shot_circuit_template + basis_circuit if (basis_circuit.size > 0) else one_shot_circuit_template + + # Frequencies returned by simulate are of the form {'0100...01': 1.0}. + # We add the bitstring to the shadow. + freqs, _ = backend.simulate(one_shot_circuit, initial_statevector=initial_statevector) + self.bitstrings += [list(freqs.keys())[0]] + + @abc.abstractmethod + def build(self): + pass + + @abc.abstractmethod + def get_basis_circuits(self, only_unique=False): + pass + + @abc.abstractmethod + def estimate_state(self): + pass + + @abc.abstractmethod + def get_term_observable(self): + pass + + +class RandomizedClassicalShadow(ClassicalShadow): + r"""Classical shadows using randomized single Pauli measurements, as defined + in H.Y. Huang, R. Kueng, and J. Preskill, Nature Physics 16, 1050 (2020). In + short, the channel is inversed to geet the state with the formula + \hat{\rho} = \bigotimes_{j=1}^n \left( 3U_j^{\dagger} |b_j\rangle \langle b_j| U_j - \mathbb{I} \right) + """ + + def build(self, n_shots): + """Random sampling of single pauli words. + + Args: + n_shots (int): Total number of measurements. + + Returns: + list of str: Measurements generated for a randomized procedure. + """ + measurement_procedure = [] + for _ in range(n_shots): + single_round_measurement = "".join([random.choice(["X", "Y", "Z"]) for _ in range(self.n_qubits)]) + measurement_procedure.append(single_round_measurement) + + self.unitaries = measurement_procedure + return measurement_procedure + + def get_basis_circuits(self, only_unique=False): + """Output a list of circuits corresponding to the random Pauli words + unitaries. + + Args: + only_unique (bool): Considering only unique unitaries. + + Returns: + list of Circuit or tuple: All basis circuits or a tuple of unique + circuits (first) with the numbers of occurence (last). + """ + + if not self.unitaries: + raise ValueError(f"A set of unitaries must de defined (can be done with the build method in {self.__class__.__name__}).") + + unitaries_to_convert = self.unique_unitaries if only_unique else self.unitaries + + basis_circuits = list() + for pauli_word in unitaries_to_convert: + # Transformation of a unitary to quantum gates. + pauli_of = pauli_string_to_of(pauli_word) + basis_circuits += [Circuit(measurement_basis_gates(pauli_of), self.n_qubits)] + + # Counting each unique circuits (use for reversing to a full shadow from an experiement on hardware). + if only_unique: + unique_basis_circuits = [(basis_circuits[i], self.unitaries.count(u)) for i, u in enumerate(unitaries_to_convert)] + return unique_basis_circuits + # Not necessary if we output everything. + else: + return basis_circuits + + def estimate_state(self, start=0, end=None, indices=None): + """Returns the classical shadow average density matrix for a range of + snapshots. + + Args: + start (int): Starting snapshot for the desired range. + end (int): Ending snapshot for the desired range. + indices (list int): Specific snapshot to pick. If this + variable is set, start and end are ignored. + + Returns: + array of complex: Estimation of the 2^n * 2^n state. + """ + + # Select specific snapshots. Default: all snapshots. + if indices is not None: + snapshot_indices = indices + else: + if end is None: + end = self.size + snapshot_indices = list(range(start, min(end, self.size))) + + # Creation of the density matrix object that snapshot rho will be added to. + rho = np.zeros((2**self.n_qubits, 2**self.n_qubits), dtype=complex) + + # Undo rotations for the selected snapshot(s). + for i_snapshot in snapshot_indices: + + # Starting point is the Identity matrix of size 1. + rho_snapshot = np.ones((1, 1), dtype=complex) + + for n in range(self.n_qubits): + state = zero_state if self.bitstrings[i_snapshot][n] == "0" else one_state + + # Unitary to undo the rotation. + U = rotations[self.unitaries[i_snapshot][n]] + bU = state @ U + rho_i = 3 * np.outer(bU.conj(), bU) - I + rho_snapshot = np.kron(rho_snapshot, rho_i) + + rho += rho_snapshot[:, :] + + # Save the result, return the state estimation + self.state_estimate = rho / len(snapshot_indices) + return self.state_estimate + + def get_term_observable(self, term, coeff=1., k=10): + """Returns the estimated observable for a term and its coefficient. + + Args: + term (tuple): Openfermion style of a qubit operator term. + coeff (float): Multiplication factor for the term. + k (int): Grouping k observations for the means of median protocol. + + Returns: + float: Observable estimated with the shadow. + """ + + shadow_size = len(self) + dict_term = dict(term) + observables_to_median = list() + + shadow_step = shadow_size // k + + # Median of average loop. + for i_snapshot in range(0, shadow_size - shadow_step, shadow_step): + observables_to_mean = np.empty(shadow_step, dtype=float) + + for j_snapshot in range(i_snapshot, i_snapshot + shadow_step): + # Uses the fact that the trace of a tensor product is the + # product of the traces of the indidual matrices + # Tr(L \otimes M) = Tr(L) * Tr(M) + # Also uses the fact that the product of two matrices that + # are tensor products of smaller matrices is the tensor + # products of the smaller matrices multiplied + # (L \otimes M) @ (O \otimes P) = (L @ O) \otimes (M @ P) + trace = 1. + for i_qubit in range(self.n_qubits): + # If there is an operation applied on the qubit n. + if i_qubit in dict_term.keys(): + obs = matrices[dict_term[i_qubit]] + tobs = traces[dict_term[i_qubit]] + else: + # Trace of identity matrix + obs = I + tobs = 2 + + state = zero_state if self.bitstrings[j_snapshot][i_qubit] == "0" else one_state + U = rotations[self.unitaries[j_snapshot][i_qubit]] + + # Make for one qubit. + left_side = obs @ right_side.conj() + + # Below is the faster way to compute + # trace *= np.trace(3*np.outer(left_side, right_side) - obs) + trace *= 3*np.dot(left_side, right_side) - tobs + + observables_to_mean[j_snapshot - i_snapshot] = np.real(coeff*trace) + observables_to_median.append(np.mean(observables_to_mean)) + + return np.median(observables_to_median) diff --git a/tangelo/toolboxes/measurements/tests/test_classical_shadows.py b/tangelo/toolboxes/measurements/tests/test_classical_shadows.py new file mode 100644 index 000000000..a8eb097e9 --- /dev/null +++ b/tangelo/toolboxes/measurements/tests/test_classical_shadows.py @@ -0,0 +1,107 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest + +import numpy as np +from qiskit.providers.aer.noise.errors.standard_errors import thermal_relaxation_error + +from tangelo.linq import Gate, Circuit, Simulator +from tangelo.toolboxes.measurements.classical_shadows import RandomizedClassicalShadow +from tangelo.toolboxes.operators.operators import QubitOperator + +# Circuit to sample (Bell state). +state = Circuit([Gate("H", 0), Gate("CNOT", 1, 0)]) + +# Simple saved bistrings and unitaries to construct a shadow (size = 100). +bitstrings = ["01", "00", "00", "01", "00", "00", "11", "11", "00", "11", "11", + "01", "00", "11", "11", "00", "11", "11", "00", "11", "11", "10", + "01", "11", "01", "00", "00", "01", "01", "11", "01", "01", "11", + "10", "00", "01", "01", "00", "00", "10", "10", "10", "00", "11", + "00", "01", "00", "11", "00", "00", "11", "11", "11", "00", "10", + "01", "01", "10", "01", "00", "00", "10", "00", "00", "10", "10", + "01", "00", "11", "01", "00", "11", "11", "00", "11", "11", "01", + "01", "01", "11", "00", "11", "10", "11", "10", "00", "00", "00", + "00", "10", "01", "10", "10", "11", "11", "11", "01", "00", "11", + "11"] +unitaries = ["ZY", "XZ", "XZ", "YZ", "ZX", "XX", "YZ", "XZ", "YX", "ZZ", "XX", + "YY", "XX", "XX", "ZZ", "YZ", "XX", "XZ", "XY", "YX", "XZ", "ZY", + "YX", "XY", "YZ", "XX", "XX", "ZY", "ZY", "XZ", "YY", "XZ", "YX", + "YY", "ZZ", "YY", "YZ", "ZX", "XY", "XY", "YY", "YY", "XZ", "YZ", + "XZ", "XZ", "ZZ", "XX", "XZ", "ZX", "ZZ", "ZX", "ZZ", "XX", "YX", + "ZX", "XY", "YY", "YY", "XX", "YX", "YZ", "XX", "ZZ", "XZ", "YY", + "YX", "ZY", "XZ", "ZX", "XX", "XX", "YX", "XY", "ZZ", "XZ", "ZX", + "ZY", "YX", "ZZ", "XX", "ZY", "YZ", "ZX", "YZ", "ZY", "XZ", "XX", + "XZ", "YZ", "YY", "ZY", "XZ", "XX", "ZY", "XX", "YY", "XY", "ZX", + "ZX"] + + +class RandomizedClassicalShadowTest(unittest.TestCase): + + def test_initialization(self): + """Testing the initialization.""" + + RandomizedClassicalShadow(state, bitstrings, unitaries) + + def test_shadow_properties(self): + """Testing of the shadow properties.""" + + cs = RandomizedClassicalShadow(state, bitstrings, unitaries) + + self.assertEqual(cs.n_qubits, 2) + self.assertEqual(cs.size, 100) + self.assertEqual(len(cs), 100) + + def test_get_term_observable(self): + """Testing the computation of a single qubit term.""" + + cs = RandomizedClassicalShadow(state, bitstrings, unitaries) + obs = cs.get_term_observable([(0, "Y"), (1, "Y")], 1., k=10) + self.assertAlmostEqual(obs, -0.89999, places=4) + + def test_get_observable(self): + """Testings the computation of an eigenvalue of a QubitOperator.""" + + cs = RandomizedClassicalShadow(state, bitstrings, unitaries) + obs = cs.get_observable(QubitOperator("Y0 Y1", coefficient=1.)) + self.assertAlmostEqual(obs, -0.89999, places=4) + + def test_estimate_state(self): + """Testing of the state estimation method to get the density matrix.""" + + cs = RandomizedClassicalShadow(state, bitstrings, unitaries) + rho_estimate = cs.estimate_state() + + # Previously ran with this specific shadow. + rho_ref = np.array([ + [ 0.445+0.j, 0.06+0.045j, 0.255+0.09j, 0.63-0.09j], + [ 0.06-0.045j, 0.07+0.j, 0.135+0.045j, -0.15-0.09j], + [ 0.255-0.09j, 0.135-0.045j, 0.025+0.j, -0.12-0.045j], + [ 0.63+0.09j, -0.15+0.09j, -0.12+0.045j, 0.46+0.j] + ]) + + np.testing.assert_array_almost_equal(rho_estimate, rho_ref) + + def test_get_basis_circuits(self): + """Testing of the method to get the appended circuit corresponding to + the unitaries. + """ + cs = RandomizedClassicalShadow(state, bitstrings, unitaries) + + self.assertEqual(len(cs.get_basis_circuits(False)), 100) + self.assertEqual(len(cs.get_basis_circuits(True)), 9) + + +if __name__ == "__main__": + unittest.main() From df0af06cef6028bcfb5265a76b56eec33e09cd60 Mon Sep 17 00:00:00 2001 From: AlexandreF-1qbit <76115575+AlexandreF-1qbit@users.noreply.github.com> Date: Mon, 10 Jan 2022 18:34:32 -0500 Subject: [PATCH 51/68] QCC ansatz fix Fixed incorrect qubit number in qmf circuit build --- tangelo/toolboxes/ansatz_generator/_qubit_mf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tangelo/toolboxes/ansatz_generator/_qubit_mf.py b/tangelo/toolboxes/ansatz_generator/_qubit_mf.py index 5a157e877..c9065df67 100644 --- a/tangelo/toolboxes/ansatz_generator/_qubit_mf.py +++ b/tangelo/toolboxes/ansatz_generator/_qubit_mf.py @@ -158,7 +158,7 @@ def get_qmf_circuit(qmf_var_params, variational=True): n_qubits, gates = qmf_var_params.size // 2, [] for idx, param in enumerate(qmf_var_params): gate_id = "RX" if idx < n_qubits else "RZ" - gates.append(Gate(gate_id, target=idx, parameter=param, is_variational=variational)) + gates.append(Gate(gate_id, target=idx % n_qubits, parameter=param, is_variational=variational)) return Circuit(gates) From 064f5ea45e68c27e6d106e04d7b07c4ef8b94172 Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Wed, 12 Jan 2022 14:03:46 -0800 Subject: [PATCH 52/68] Support for name attribute in Circuit class (#110) * Support for name attribute in Circuit class --- tangelo/linq/circuit.py | 11 +++++---- tangelo/linq/gate.py | 13 ++--------- tangelo/linq/tests/test_translator.py | 33 ++++++++++++--------------- 3 files changed, 24 insertions(+), 33 deletions(-) diff --git a/tangelo/linq/circuit.py b/tangelo/linq/circuit.py index 05b0408e8..3220874bf 100644 --- a/tangelo/linq/circuit.py +++ b/tangelo/linq/circuit.py @@ -44,9 +44,10 @@ class Circuit: the example folder. """ - def __init__(self, gates: List[Gate] = None, n_qubits=None): + def __init__(self, gates: List[Gate] = None, n_qubits=None, name="no_name"): """Initialize gate list and internal variables depending on user input.""" + self.name = name self._gates = list() self._qubits_simulated = n_qubits self._qubit_indices = set() if not n_qubits else set(range(n_qubits)) @@ -246,11 +247,13 @@ def inverse(self): Returns: Circuit: the inverted circuit """ - gate_list = [gate.inverse() for gate in reversed(self._gates)] - return Circuit(gate_list) + gates = [gate.inverse() for gate in reversed(self._gates)] + return Circuit(gates, n_qubits=self.width) def serialize(self): - return {"type": "QuantumCircuit", "gates": [gate.serialize() for gate in self._gates]} + if not isinstance(self.name, str): + return TypeError("Name of circuit object must be a string") + return {"name": self.name, "type": "QuantumCircuit", "gates": [gate.serialize() for gate in self._gates]} def stack(*circuits): diff --git a/tangelo/linq/gate.py b/tangelo/linq/gate.py index 78988227e..865e928f7 100644 --- a/tangelo/linq/gate.py +++ b/tangelo/linq/gate.py @@ -125,19 +125,10 @@ def inverse(self): new_parameter = -self.parameter elif self.name in {"T", "S"}: new_parameter = -pi / 2 if self.name == "T" else -pi / 4 - return Gate(name="PHASE", - target=self.target, - control=self.control, - parameter=new_parameter, - is_variational=self.is_variational) - + return Gate("PHASE", self.target, self.control, new_parameter, self.is_variational) else: raise AttributeError(f"{self.name} is not an invertible gate when parameter is {self.parameter}") - return Gate(name=self.name, - target=self.target, - control=self.control, - parameter=new_parameter, - is_variational=self.is_variational) + return Gate(self.name, self.target, self.control, new_parameter, self.is_variational) def serialize(self): return {"type": "Gate", diff --git a/tangelo/linq/tests/test_translator.py b/tangelo/linq/tests/test_translator.py index ff15c474d..ac6f46355 100644 --- a/tangelo/linq/tests/test_translator.py +++ b/tangelo/linq/tests/test_translator.py @@ -375,6 +375,21 @@ def test_json_ionq(self): assert(json_ionq_circ == ref_circuit) + def test_translate_ionq_inverse(self): + """ Test that inverse of T and S circuits for ionQ return Tdag and Sdag after translation """ + + # Generate [Gate("Tdag", 0), Gate("Sdag", 0)] equivalent, and its hardcoded inverse + circ = Circuit([Gate("PHASE", 0, parameter=-np.pi/4), Gate("PHASE", 0, parameter=-np.pi/2)]) + inverse_circ = Circuit([Gate("S", 0), Gate("T", 0)]) + + ionq_circ_inverse = translator.translate_json_ionq(circ.inverse()) + ionq_inverse_circ = translator.translate_json_ionq(inverse_circ) + ionq_circ = translator.translate_json_ionq(circ) + + ionq_ref = {'qubits': 1, 'circuit': [{'gate': 'ti', 'target': 0}, {'gate': 'si', 'target': 0}]} + self.assertTrue(ionq_inverse_circ == ionq_circ_inverse) + self.assertTrue(ionq_circ == ionq_ref) + @unittest.skipIf("braket" not in installed_backends, "Test Skipped: Backend not available \n") def test_braket(self): """ @@ -424,24 +439,6 @@ def test_unsupported_gate(self): circ = Circuit([Gate("Potato", 0)]) self.assertRaises(ValueError, translator.translate_qiskit, circ) - def test_translate_ionq_inverse(self): - """ Test that inverse of T and S circuits for ionQ return Tdag and Sdag after translation """ - - # Generate [Gate("Tdag", 0), Gate("Sdag", 0)] equivalent - circ = Circuit([Gate("PHASE", 0, parameter=-np.pi/4), Gate("PHASE", 0, parameter=-np.pi/2)]) - # Hard-coded inverse - inverse_circ = Circuit([Gate("S", 0), Gate("T", 0)]) - - ionq_circ_inverse = translator.translate_json_ionq(circ.inverse()) - ionq_inverse_circ = translator.translate_json_ionq(inverse_circ) - ionq_circ = translator.translate_json_ionq(circ) - # Hard-coded circuit dictionary - ionq_circ_dict = {'qubits': 1, 'circuit': [{'gate': 'ti', 'target': 0}, {'gate': 'si', 'target': 0}]} - - # ionq uses a dictionary to store circuits, convert to str and compare - self.assertEqual(str(ionq_inverse_circ), str(ionq_circ_inverse)) - self.assertEqual(str(ionq_circ), str(ionq_circ_dict)) - if __name__ == "__main__": unittest.main() From 624bff767167fc26bf42bc41a10f5aa70f1c4397 Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Fri, 14 Jan 2022 11:22:08 -0800 Subject: [PATCH 53/68] Links fixes in main project files (#112) * Fixed links with new repo URL --- CONTRIBUTIONS.rst | 16 ++++++++-------- README.rst | 16 +++++++++------- setup.py | 8 ++++---- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/CONTRIBUTIONS.rst b/CONTRIBUTIONS.rst index b93e3009d..dfef228dc 100644 --- a/CONTRIBUTIONS.rst +++ b/CONTRIBUTIONS.rst @@ -5,7 +5,7 @@ Thank you very much for considering contributing to this project; we’d love to Do not feel intimidated by the guidelines and processes we describe in this document: we are here to assist you and help you take things to the finish line. We do not expect you to be an expert in software development or to get everything right on the first attempt: don’t hesitate to open an issue or a pull request, or simply contact us. -Contributors have various backgounds and experience, from high schoolers to fully fledged quantum scientists or chemists, and there are many ways you can contribute to this project. You can of course open a pull request (PR) and extend our codebase, but opening an issue to suggest a new feature, report a bug, improve our documentation or make a Jupyter notebook is just as valuable. +Contributors have various backgounds and experience, from high schoolers to fully fledged quantum scientists or chemists, and there are many ways you can contribute to this project. You can of course open a pull request (PR) and extend our codebase, but opening an issue to suggest a new feature, report a bug, improve our documentation or make a tutorial notebook is just as valuable. By joining the Tangelo community and sharing your ideas and developments, you are creating an opportunity for us to learn and grow together, and take ideas to the finish line and beyond. @@ -27,22 +27,22 @@ All submissions to the Github repository are subject to review by qualified proj **1. Set up your fork** -Go to the `main Tangelo repo `_ and click the Fork button in the upper right corner of the screen. -This creates a new Github repo ``https://github.com/USERNAME/tangelo`` where ``USERNAME`` is your Github ID. +Go to the `main Tangelo repo `_ and click the Fork button in the upper right corner of the screen. +This creates a new Github repo ``https://github.com/USERNAME/Tangelo`` where ``USERNAME`` is your Github ID. -In your terminal, clone the repo on your local machine, and move into the newly created directory: +In your terminal, clone the repo on your local machine, and move into the newly created directory (replace ``USERNAME`` with your user ID) .. code-block:: shell - git clone https://github.com/quantumsimulation/QEMIST_qSDK.git - cd QEMISK_qSDK + git clone https://github.com/USERNAME/Tangelo.git + cd Tangelo From the perspective of your local clone, your fork is called the ``origin`` remote. Let's synchronize your fork with the main Tangelo repo by adding the latter as the upstream remote, and then update your local ``main`` branch: .. code-block:: shell - git remote add upstream https://github.com/quantumsimulation/QEMIST_qSDK.git + git remote add upstream https://github.com/goodchemistryco/Tangelo.git git fetch upstream git checkout main @@ -67,7 +67,7 @@ Let's assume you've made some changes and committed them with ``git commit``, an **3. The Pull Request (PR)** -Now when you go to https://github.com/quantumsimulation/QEMIST_qSDK, you should be able to create a pull request from the branch on your fork to a branch on the main Tangelo repo. Give your pull request a name and briefly describe what the purpose is and include a reference to the associated issue if there's one. +Now when you go to https://github.com/goodchemistryco/Tangelo, you should be able to create a pull request from the branch on your fork to a branch on the main Tangelo repo. Give your pull request a name and briefly describe what the purpose is and include a reference to the associated issue if there's one. Several Tangelo users will receive a notification, and will review your code and leave comments in the PR. You can reply to these comments, or simply apply the recommended changes locally, and then commit and push them like above: it automatically updates your PR. If there are conflicts, you can solve them locally and push, or directly through Github. diff --git a/README.rst b/README.rst index a4adb2891..a07cf79ad 100644 --- a/README.rst +++ b/README.rst @@ -7,15 +7,18 @@ Tangelo overview |dev_branch| .. |maintainer| image:: https://img.shields.io/badge/Maintainer-GoodChemistry-blue + :target: https://goodchemistry.com .. |licence| image:: https://img.shields.io/badge/License-Apache_2.0-green - :target: https://github.com/quantumsimulation/QEMIST_qSDK/blob/main/README.rst -.. |build| image:: https://github.com/quantumsimulation/QEMIST_qSDK/actions/workflows/continuous_integration.yml/badge.svg + :target: https://github.com/goodchemistryco/Tangelo/blob/main/LICENSE +.. |build| image:: https://github.com/goodchemistryco/Tangelo/actions/workflows/continuous_integration.yml/badge.svg + :target: https://github.com/goodchemistryco/Tangelo/actions/workflows/continuous_integration.yml .. |dev_branch| image:: https://img.shields.io/badge/DevBranch-staging_0.3.0-yellow - Welcome ! -Tangelo is an open-source python package developed by Good Chemistry Company, focused on the development of end-to-end material simulation workflows on quantum computers. Its modular design and ease-of-use enables users to easily assemble custom workflows, tinker and define their own building blocks, while keeping track of quantum resource requirements, such as number of qubits, gates or measurements. Through problem decomposition techniques, users can scale up beyond toy models and study the impact of quantum computing on more industrially-relevant use cases. Tangelo is backend-agnostic and compatible with many existing open-source frameworks, making the integration of third-party tools such as state-of-the-art simulators, circuit compilers or quantum cloud services straightforward. It is our wish to develop a community around Tangelo, collaborate, and together leverage the best of what the field has to offer. +Tangelo is an open-source python package developed by `Good Chemistry Company `_, focusing on the development of end-to-end material simulation workflows on quantum computers. + +Its modular design and ease-of-use enables users to easily assemble custom workflows, tinker and define their own building blocks, while keeping track of quantum resource requirements, such as number of qubits, gates or measurements. Through problem decomposition techniques, users can scale up beyond toy models and study the impact of quantum computing on more industrially-relevant use cases. Tangelo is backend-agnostic and compatible with many existing open-source frameworks, making the integration of third-party tools such as state-of-the-art simulators, circuit compilers or quantum cloud services straightforward. It is our wish to develop a community around Tangelo, collaborate, and together leverage the best of what the field has to offer. Install @@ -82,8 +85,7 @@ TODO: insert sentence and link to sphinx documentation when its online. Tutorials --------- -Please check the ``examples`` folder jupyter notebook tutorials and other examples. - +Please check the ``examples`` folder for jupyter notebook tutorials and other examples. Tests ----- @@ -108,4 +110,4 @@ If you use Tangelo in your research, please cite [TODO: this is a placeholder for our Tangelo paper, to be written and put on arxiv in October] -Copyright Good Chemistry Company 2021. This software is released under the Apache Software License version 2.0. +© Good Chemistry Company 2021. This software is released under the Apache Software License version 2.0. diff --git a/setup.py b/setup.py index 8ab5666ee..d653c1eb8 100755 --- a/setup.py +++ b/setup.py @@ -17,12 +17,12 @@ def install(package): setuptools.setup( name="tangelo", author="The Tangelo developers", - version="0.2.0", - description="Open-source quantum SDK developed for exploring quantum chemistry simulation end-to-end workflows on " - "gate-model quantum computers", + version="0.3.0", + description="Tangelo is a python package developed by Good Chemistry Company, focusing on the development " + "of end-to-end materials simulation workflows on quantum computers.", long_description=long_description, long_description_content_type="text/x-rst", - url="https://github.com/quantumsimulation/QEMIST_Tangelo", + url="https://github.com/goodchemistryco/Tangelo", packages=setuptools.find_packages(), test_suite="tangelo", setup_requires=['h5py'], From 1b1f704de678807afbcdb1de96314a1d89342ce3 Mon Sep 17 00:00:00 2001 From: AlexandreF-1qbit <76115575+AlexandreF-1qbit@users.noreply.github.com> Date: Mon, 17 Jan 2022 18:35:42 -0500 Subject: [PATCH 54/68] Derandomized + Adaptive Classical Shadows (#111) * Derandomized + Adaptative CS Co-authored-by: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> --- tangelo/toolboxes/measurements/__init__.py | 4 + .../classical_shadows/__init__.py | 13 + .../classical_shadows/adaptive.py | 208 +++++++++++++++ .../classical_shadows/classical_shadows.py | 142 ++++++++++ .../classical_shadows/derandomized.py | 248 ++++++++++++++++++ .../randomized.py} | 143 +--------- .../tests/test_adaptive_classical_shadows.py | 95 +++++++ .../test_derandomized_classical_shadows.py | 86 ++++++ ...y => test_randomized_classical_shadows.py} | 7 +- 9 files changed, 807 insertions(+), 139 deletions(-) create mode 100644 tangelo/toolboxes/measurements/classical_shadows/__init__.py create mode 100644 tangelo/toolboxes/measurements/classical_shadows/adaptive.py create mode 100644 tangelo/toolboxes/measurements/classical_shadows/classical_shadows.py create mode 100644 tangelo/toolboxes/measurements/classical_shadows/derandomized.py rename tangelo/toolboxes/measurements/{classical_shadows.py => classical_shadows/randomized.py} (59%) create mode 100644 tangelo/toolboxes/measurements/tests/test_adaptive_classical_shadows.py create mode 100644 tangelo/toolboxes/measurements/tests/test_derandomized_classical_shadows.py rename tangelo/toolboxes/measurements/tests/{test_classical_shadows.py => test_randomized_classical_shadows.py} (93%) diff --git a/tangelo/toolboxes/measurements/__init__.py b/tangelo/toolboxes/measurements/__init__.py index fa5894672..6c1fd1a56 100644 --- a/tangelo/toolboxes/measurements/__init__.py +++ b/tangelo/toolboxes/measurements/__init__.py @@ -14,3 +14,7 @@ from .qubit_terms_grouping import group_qwc, exp_value_from_measurement_bases from .estimate_measurements import get_measurement_estimate +from .classical_shadows.classical_shadows import ClassicalShadow +from .classical_shadows.randomized import RandomizedClassicalShadow +from .classical_shadows.derandomized import DerandomizedClassicalShadow +from .classical_shadows.adaptive import AdaptiveClassicalShadow diff --git a/tangelo/toolboxes/measurements/classical_shadows/__init__.py b/tangelo/toolboxes/measurements/classical_shadows/__init__.py new file mode 100644 index 000000000..71e854239 --- /dev/null +++ b/tangelo/toolboxes/measurements/classical_shadows/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/tangelo/toolboxes/measurements/classical_shadows/adaptive.py b/tangelo/toolboxes/measurements/classical_shadows/adaptive.py new file mode 100644 index 000000000..7e7e70223 --- /dev/null +++ b/tangelo/toolboxes/measurements/classical_shadows/adaptive.py @@ -0,0 +1,208 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This file provides an API enabling the use of adaptive classical shadows. +This algorithm is described in C. Hadfield, ArXiv:2105.12207 [Quant-Ph] (2021). +""" + +from math import sqrt +import random + +import numpy as np + +from tangelo.toolboxes.measurements import ClassicalShadow +from tangelo.linq.circuit import Circuit +from tangelo.linq.helpers.circuits.measurement_basis import measurement_basis_gates, pauli_string_to_of + + +class AdaptiveClassicalShadow(ClassicalShadow): + """Classical shadows using adaptive single Pauli measurements, as defined + in C. Hadfield, ArXiv:2105.12207 [Quant-Ph] (2021). + """ + + def build(self, n_shots, qu_op): + """Adaptive classical shadow building method to define relevant + unitaries depending on the qubit operator. + + Args: + n_shots (int): The number of desired measurements. + qu_op (QubitOperator): The observable that one wishes to measure. + + Returns: + list of str: The list of Pauli words that describes the measurement + basis to use. + """ + + measurement_procedure = [self._choose_measurement(qu_op) for _ in range(n_shots)] + + self.unitaries = measurement_procedure + return measurement_procedure + + def _choose_measurement(self, qu_op): + """Algorithm 1 from the publication. + + Args: + qu_op (QubitOperator): The operator that one wishes to maximize the + measurement budget over. + + Returns: + str: Pauli words for one measurement. + """ + + # Random bijection i: [n] -> [n]. Also, compute the inverse to undo it. + i_qubit_random = random.sample(range(self.n_qubits), self.n_qubits) + inverse_map = np.argsort(i_qubit_random) + + single_measurement = [None] * self.n_qubits + + # Choose measurement one qubit at the time. + for it, i_qubit in enumerate(i_qubit_random): + probs = self._get_probs(qu_op, + i_qubit_random[0:it], + single_measurement[0:it], + i_qubit) + + single_measurement[it] = np.random.choice(["X", "Y", "Z"], size=None, replace=True, p=probs) + + # Reorder according to the qubit indices 0, 1, 2, ... self.n_qubits. + reordered_measurement = [single_measurement[inverse_map[j]] for j in range(self.n_qubits)] + + return "".join(reordered_measurement) + + def _get_probs(self, qu_op, prev_qubits, prev_paulis, curr_qubit): + """Generates the betas values from which the Pauli basis is determined + for the current qubit (curr_qubit), as shown in Algorithm 2 from the + paper. + + Args: + qu_op (QubitOperator) : The operator one wishes to get the + expectation value of. + prev_qubits (list) : list of previous qubits from which the + measurement basis is already determined. + prev_paulis (list) : the Pauli word for prev_qubits. + curr_qubit (int) : The current qubit being examined. + + Returns: + list of float: cB values for X, Y and Z. + """ + + cbs = {"X": 0., "Y": 0., "Z": 0.} + + # Builds the candidate term (appending X, Y or Z). Then, transform + # to a dictionary for removing qubit order dependency. + B = dict(zip(prev_qubits, prev_paulis)) + + for basis in cbs.keys(): + + # Adds or overwrites the X, Y or Z prospect term. + B[curr_qubit] = basis + + # Checks if term (P) is covered by candidate_pauli (B). P and B are + # notation in the publication. + for term, coeff in qu_op.terms.items(): + if not term: + continue + + # Like for B, remove qubit order dependency. + P = dict(term) + + # Checks if an entry is in both dictionaries and compares the + # values. If values are different, an entry is appended to + # non_shared_items. If the key is not in P it is not. It means + # that it is I for this qubit (so it does not break the cover + # condition). + non_shared_items = {k: B[k] for k in B if k in P and P[k] != B[k]} + + # If there are non-overlapping terms P_i not in {I, B_i(j)}, + # we do not take into account the term coefficient. + if not non_shared_items: + cbs[basis] += coeff**2 + + cbs = {basis: sqrt(cb) for basis, cb in cbs.items()} + + if sum(cbs.values()) < 1e-6: + # Uniform distribution. + probs = [1/3] * 3 + else: + # Normalization + make sure there are in X, Y and Z order (eq. 3). + sum_squared_cbs = sum([sqrt(cb) for cb in cbs.values()]) + probs = [sqrt(cbs[pauli]) / sum_squared_cbs for pauli in ["X", "Y", "Z"]] + + return probs + + def get_basis_circuits(self, only_unique=False): + """Outputs a list of circuits corresponding to the adaptive single-Pauli + unitaries. + + Args: + only_unique (bool): Consider only unique unitaries. + + Returns: + list of Circuit or tuple: All basis circuits or a tuple of unique + circuits (first) with the numbers of occurence (last). + """ + + if not self.unitaries: + raise ValueError(f"A set of unitaries must de defined (can be done with the build method in {self.__class__.__name__}).") + + unitaries_to_convert = self.unique_unitaries if only_unique else self.unitaries + + basis_circuits = list() + for pauli_word in unitaries_to_convert: + # Transformation of a unitary to quantum gates. + pauli_of = pauli_string_to_of(pauli_word) + basis_circuits += [Circuit(measurement_basis_gates(pauli_of), self.n_qubits)] + + # Counts each unique circuits (use for reversing to a full shadow from + # an experiement on hardware). + if only_unique: + unique_basis_circuits = [(basis_circuits[i], self.unitaries.count(u)) for i, u in enumerate(unitaries_to_convert)] + return unique_basis_circuits + else: + return basis_circuits + + def get_term_observable(self, term, coeff=1.): + """Returns the estimated observable for a term and its coefficient. + + Args: + term (tuple): Openfermion style of a qubit operator term. + coeff (float): Multiplication factor for the term. + + Returns: + float: Observable estimated with the shadow. + """ + + sum_product = 0 + n_match = 0 + + # For every single_measurement in shadow_size. + for snapshot in range(self.size): + match = 1 + product = 1 + + # Checks if there is a match for all Pauli gate in the term. Works + # also with operator not on all qubits (e.g. X1 will hit Z0X1, Y0X1 + # and Z0X1). + for i_qubit, pauli in term: + if pauli != self.unitaries[snapshot][i_qubit]: + match = 0 + break + if self.bitstrings[snapshot][i_qubit] != "0": + product *= -1 + + # No quantity is considered if there is no match. + sum_product += match * product + n_match += match + + return sum_product / n_match * coeff if n_match > 0 else 0. diff --git a/tangelo/toolboxes/measurements/classical_shadows/classical_shadows.py b/tangelo/toolboxes/measurements/classical_shadows/classical_shadows.py new file mode 100644 index 000000000..b620e63ca --- /dev/null +++ b/tangelo/toolboxes/measurements/classical_shadows/classical_shadows.py @@ -0,0 +1,142 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This file provides an API enabling the use of classical shadows. The original +idea is described in H.Y. Huang, R. Kueng, and J. Preskill, Nature Physics 16, +1050 (2020). +""" + +import abc +import warnings + +from tangelo.linq.circuit import Circuit + + +class ClassicalShadow(abc.ABC): + """Abstract class for the classical shadows implementation. Classical + shadows is a mean to characterize a quantum state (within an error treshold) + with the fewest measurement possible. + """ + + def __init__(self, circuit, bitstrings=None, unitaries=None): + """Default constructor for the ClassicalShadow object. This class is + the parent class for the different classical shadows flavors. The object + is defined by the bistrings and unitaries used in the process. Abstract + methods are defined to take into account the procedure to inverse the + channel. + + Args: + bistrings (list of str): Representation of the outcomes for all + snapshots. E.g. ["11011", "10000", ...]. + unitaries (list of str): Representation of the unitary for every + snapshot, used to reverse the channel. + """ + + self.circuit = circuit + self.bitstrings = list() if bitstrings is None else bitstrings + self.unitaries = list() if unitaries is None else unitaries + + # If the state has been estimated, it is stored into this attribute. + self.state_estimate = None + + @property + def n_qubits(self): + """Returns the number of qubits the shadow represents.""" + return self.circuit.width + + @property + def size(self): + """Number of shots used to make the shadow.""" + return len(self.bitstrings) + + @property + def unique_unitaries(self): + """Returns the list of unique unitaries.""" + return list(set(self.unitaries)) + + def __len__(self): + """Same as the shadow size.""" + return self.size + + def append(self, bitstring, unitary): + """Append method to merge new snapshots to an existing shadow. + + Args: + bistring (str or list of str): Representation of outcomes. + unitary (str or list of str): Relevant unitary for those outcomes. + """ + if isinstance(bitstring, list) and isinstance(unitary, list): + assert len(bitstring) == len(unitary) + self.bitstrings += bitstring + self.unitaries += unitary + elif isinstance(bitstring, str) and isinstance(unitary, str): + self.bitstrings.append(bitstring) + self.unitaries.append(unitary) + else: + raise ValueError("bistring and unitary arguments must be consistent strings or list of strings.") + + def get_observable(self, qubit_op, *args, **kwargs): + """Getting an estimated observable value for a qubit operator from the + classical shadow. This function loops through all terms and calls, for + each of them, the get_term_observable method defined in the child class. + Other arguments (args, kwargs) can be passed to the method. + + Args: + qubit_op (QubitOperator): Operator to estimate. + """ + observable = 0. + for term, coeff in qubit_op.terms.items(): + observable += self.get_term_observable(term, coeff, *args, **kwargs) + + return observable + + def simulate(self, backend, initial_statevector=None): + """Simulate, using a predefined backend, a shadow from a circuit or a + statevector. + + Args: + backend (Simulator): Backend for the simulation of a shadow. + initial_statevector(list/array) : A valid statevector in the format + supported by the target backend. + """ + + if not self.unitaries: + raise ValueError(f"The build method of {self.__class__.__name__} must be called before simulation.") + + if backend.n_shots != 1: + warnings.warn(f"Changing number of shots to 1 for the backend (classical shadows).") + backend.n_shots = 1 + + # Different behavior if circuit or initial_statevector is defined. + one_shot_circuit_template = self.circuit if self.circuit is not None else Circuit(n_qubits=self.n_qubits) + + for basis_circuit in self.get_basis_circuits(only_unique=False): + one_shot_circuit = one_shot_circuit_template + basis_circuit if (basis_circuit.size > 0) else one_shot_circuit_template + + # Frequencies returned by simulate are of the form {'0100...01': 1.0}. + # We add the bitstring to the shadow. + freqs, _ = backend.simulate(one_shot_circuit, initial_statevector=initial_statevector) + self.bitstrings += [list(freqs.keys())[0]] + + @abc.abstractmethod + def build(self): + pass + + @abc.abstractmethod + def get_basis_circuits(self, only_unique=False): + pass + + @abc.abstractmethod + def get_term_observable(self): + pass diff --git a/tangelo/toolboxes/measurements/classical_shadows/derandomized.py b/tangelo/toolboxes/measurements/classical_shadows/derandomized.py new file mode 100644 index 000000000..d4d5a6851 --- /dev/null +++ b/tangelo/toolboxes/measurements/classical_shadows/derandomized.py @@ -0,0 +1,248 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This file provides an API enabling the use of derandomized classical shadows. +This algorithm is described in H.-Y. Huang, R. Kueng, and J. Preskill, +ArXiv:2103.07510 [Quant-Ph] (2021). +""" + +from math import floor, exp, log +import random + +from tangelo.toolboxes.measurements import ClassicalShadow +from tangelo.linq.circuit import Circuit +from tangelo.linq.helpers.circuits.measurement_basis import measurement_basis_gates, pauli_string_to_of + + +class DerandomizedClassicalShadow(ClassicalShadow): + """Classical shadows using derandomized single Pauli measurements, as + defined in H.-Y. Huang, R. Kueng, and J. Preskill, ArXiv:2103.07510 + [Quant-Ph] (2021). + """ + + def build(self, n_shots, qu_op, eta=0.9): + """Derandomized sampling of single Pauli words. + + Args: + n_shots (int): Total number of measurements. + qu_op (QubitOperator): Relevant QubitOperator. + eta (float): Empirical parameter for the cost function. Default is + 0.9. + + Returns: + list of str: Measurements generated for a derandomized procedure. + """ + + # Getting the weights, proportional to the coefficient of each Pauli + # words. Some variables are defined to normalize the weights and track + # the amount of measurements already defined by the algorithm. + observables, weights = zip(*[(obs, abs(w)) for obs, w in qu_op.terms.items() if obs]) + + n_observables = len(observables) + n_measurements_per_observable = floor(n_shots / n_observables) + + norm_factor = n_observables / sum(weights) + weights = [w * norm_factor for w in weights] + + weighted_n_measurements_per_observable = [floor(weights[i]*n_measurements_per_observable) for i in range(n_observables)] + + n_measurements_so_far = [0] * n_observables + + # Output variable (containing all chosen basis). + measurement_procedure = list() + + # Distribution of n_measurements_per_observable * n_observables shots. + for _ in range(n_measurements_per_observable * n_observables): + + # A single round of parallel measurements over all qubits. + n_matches_needed_round = [len(p) for p in observables] + + single_round_measurement = [None] * self.n_qubits + + # Optimizing which Pauli basis to use for each qubit according to + # self._cost_function. + for i_qubit in range(self.n_qubits): + cost_of_outcomes = {"X": 0, "Y": 0, "Z": 0} + + # Computing the cost function with all the possibilities. + for dice_roll_pauli in ["Z", "X", "Y"]: + # Assume the dice rollout to be dice_roll_pauli. + try_matches_needed_round = n_matches_needed_round.copy() + for i_obs, obs in enumerate(observables): + try_matches_needed_round[i_obs] += _get_match_up(i_qubit, dice_roll_pauli, obs, self.n_qubits) + + cost_of_outcomes[dice_roll_pauli] = self._cost_function(n_measurements_so_far, + try_matches_needed_round, + weights, + weighted_n_measurements_per_observable, + eta) + + # Determining the single Pauli gate to use. + for dice_roll_pauli in ["Z", "X", "Y"]: + if min(cost_of_outcomes.values()) < cost_of_outcomes[dice_roll_pauli]: + continue + + # The best dice roll outcome will be chosen here. + single_round_measurement[i_qubit] = dice_roll_pauli + + for i_obs, obs in enumerate(observables): + n_matches_needed_round[i_obs] += _get_match_up(i_qubit, dice_roll_pauli, obs, self.n_qubits) + break + + measurement_procedure.append(single_round_measurement) + + # Incrementing the number of measurements so far if there is no more + # matches to make this round. + n_measurements_so_far = [n[0] + 1 if n[1] == 0 else n[0] for n in zip(n_measurements_so_far, n_matches_needed_round)] + + # Incrementing success variable if number of measurements so far is + # bigger than the weighted number of measurements per observable. + success = sum([1 for i in range(n_observables) if n_measurements_so_far[i] >= weighted_n_measurements_per_observable[i]]) + + if success == n_observables: + break + + measurement_procedure = ["".join(m) for m in measurement_procedure] + + # Fill "missing" shots with a set of random (already chosen) basis. + measurement_procedure += random.choices(measurement_procedure, k=n_shots-len(measurement_procedure)) + + self.unitaries = measurement_procedure + return measurement_procedure + + def get_basis_circuits(self, only_unique=False): + """Outputs a list of circuits corresponding to the chosen single-Pauli + unitaries. + + Args: + only_unique (bool): Considering only unique unitaries. + + Returns: + list of Circuit or tuple: All basis circuits or a tuple of unique + circuits (first) with the numbers of occurence (last). + """ + + if not self.unitaries: + raise ValueError(f"A set of unitaries must de defined (can be done with the build method in {self.__class__.__name__}).") + + unitaries_to_convert = self.unique_unitaries if only_unique else self.unitaries + + basis_circuits = list() + for pauli_word in unitaries_to_convert: + # Transformation of a unitary to quantum gates. + pauli_of = pauli_string_to_of(pauli_word) + basis_circuits += [Circuit(measurement_basis_gates(pauli_of), self.n_qubits)] + + # Counting each unique circuits (use for reversing to a full shadow from an experiement on hardware). + if only_unique: + unique_basis_circuits = [(basis_circuits[i], self.unitaries.count(u)) for i, u in enumerate(unitaries_to_convert)] + return unique_basis_circuits + else: + return basis_circuits + + def get_term_observable(self, term, coeff=1.): + """Returns the estimated observable for a term and its coefficient. + + Args: + term (tuple): Openfermion style of a qubit operator term. + coeff (float): Multiplication factor for the term. + + Returns: + float: Observable estimated with the shadow. + """ + + sum_product = 0 + n_match = 0 + + # For every single_measurement in shadow_size. + for snapshot in range(self.size): + match = 1 + product = 1 + + # Checks if there is a match for all Pauli gate in the term. Works + # also with operator not on all qubits (e.g. X1 will hit Z0X1, Y0X1 + # and Z0X1). + for i_qubit, pauli in term: + if pauli != self.unitaries[snapshot][i_qubit]: + match = 0 + break + if self.bitstrings[snapshot][i_qubit] != "0": + product *= -1 + + # No quantity is considered if there is no match. + sum_product += match * product + n_match += match + + return sum_product / n_match * coeff if n_match > 0 else 0. + + def _cost_function(self, n_measurements_so_far, n_matches_needed, weights, weighted_num_measurements_per_observable, eta=0.9): + """Cost function for derandomized Pauli measurements, according to + equation (6) in the cited paper. + + Args: + n_measurements_so_far (list of int): Number of measurements decided + per terms. + n_matches_needed (list of int): Number of matches. + weights (list of float): Coefficient (absolute) of each each term. + weighted_num_measurements_per_observable (list of float): Weighted + number of measurements per term. + eta (float): Empirical parameter, default set to 0.9. + + Returns: + float: Output the cost considering the arguments. + """ + + # Computing the cost. Variables names are consistent of what is found in + # the publication. + nu = 1 - exp(-eta / 2) + + cost = 0. + for (n_measure, n_match, weight, weight_per_obs) in zip(n_measurements_so_far, + n_matches_needed, + weights, + weighted_num_measurements_per_observable): + + if n_measure >= weight_per_obs: + continue + + v = eta / 2 * n_measure + if self.n_qubits >= n_match: + v -= log(1 - nu / (3**n_match)) + + cost += exp(-v / weight) + + return cost + + +def _get_match_up(lookup_qubit, dice_roll_pauli, observable, n_qubits): + """Helper function to output 0, -1 or a large number depending on the + index provided and the Pauli gate in a single observable. + + Args: + lookup_qubit (int): Qubit index. + dice_roll_pauli (str): Z, X or Y. + observable (tuple): Single term in the form ((0, "Y"), (1, "Z"), + (2, "X"), ...). + n_qubits (int): Number of qubits. + + Returns: + int: 0, -1, or a large int used in computing cost. + """ + + large_number = 100 * (n_qubits + 10) + + for i_qubit, pauli in observable: + if lookup_qubit == i_qubit: + return -1 if pauli == dice_roll_pauli else large_number + return 0 diff --git a/tangelo/toolboxes/measurements/classical_shadows.py b/tangelo/toolboxes/measurements/classical_shadows/randomized.py similarity index 59% rename from tangelo/toolboxes/measurements/classical_shadows.py rename to tangelo/toolboxes/measurements/classical_shadows/randomized.py index 9e4fc2cbc..a4a44ecb8 100644 --- a/tangelo/toolboxes/measurements/classical_shadows.py +++ b/tangelo/toolboxes/measurements/classical_shadows/randomized.py @@ -12,21 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""This file provides an API enabling the use of classical shadows. The original -idea is described in H.Y. Huang, R. Kueng, and J. Preskill, Nature Physics 16, -1050 (2020). +"""This file provides an API enabling the use of randomized classical shadows. +This algorithm is described in H.Y. Huang, R. Kueng, and J. Preskill, Nature +Physics 16, 1050 (2020). """ -import abc import random -import warnings import numpy as np +from tangelo.toolboxes.measurements import ClassicalShadow from tangelo.linq.circuit import Circuit from tangelo.linq.helpers.circuits.measurement_basis import measurement_basis_gates, pauli_string_to_of - # State |0> or |1>. zero_state = np.array([1, 0]) one_state = np.array([0, 1]) @@ -36,141 +34,17 @@ X = np.array([[0, 1], [1, 0]]) Y = np.array([[0, -1j], [1j, 0]], dtype=complex) Z = np.array([[1, 0], [0, -1]]) -matrices = {"X": X, "Y": Y, "Z": Z} +pauli_matrices = {"X": X, "Y": Y, "Z": Z} # Traces of each Pauli matrices. -traces = {pauli: np.trace(matrix) for pauli, matrix in matrices.items()} +pauli_traces = {pauli: np.trace(matrix) for pauli, matrix in pauli_matrices.items()} # Reverse channels to undo single Pauli rotations. S_T = np.array([[1, 0], [0, -1j]], dtype=complex) H = np.array([[1, 1], [1, -1]]) / np.sqrt(2) -I = np.array([[1, 0], [0, 1]]) rotations = {"X": H, "Y": H @ S_T, "Z": I} -class ClassicalShadow(abc.ABC): - """Abstract class for the classical shadows implementation. Classical - shadows is a mean to characterize a quantum state (within an error treshold) - with the fewest measurement possible. - """ - - def __init__(self, circuit, bitstrings=None, unitaries=None): - """Default constructor for the ClassicalShadow object. This class is - the parent class for the different classical shadows flavors. The object - is defined by the bistrings and unitaries used in the process. Abstract - methods are defined to take into account the procedure to inverse the - channel. - - Args: - bistrings (list of str): Representation of the outcomes for all - snapshots. E.g. ["11011", "10000", ...]. - unitaries (list of str): Representation of the unitary for every - snapshot, used to reverse the channel. - """ - - self.circuit = circuit - self.bitstrings = list() if bitstrings is None else bitstrings - self.unitaries = list() if unitaries is None else unitaries - - # If the state has been estimated, it is stored into this attribute. - self.state_estimate = None - - @property - def n_qubits(self): - """Returns the number of qubits the shadow represents.""" - return self.circuit.width - - @property - def size(self): - """Number of shots used to make the shadow.""" - return len(self.bitstrings) - - @property - def unique_unitaries(self): - """Returns the list of unique unitaries.""" - return list(set(self.unitaries)) - - def __len__(self): - """Same as the shadow size.""" - return self.size - - def append(self, bitstring, unitary): - """Append method to merge new snapshots to an existing shadow. - - Args: - bistring (str or list of str): Representation of outcomes. - unitary (str or list of str): Relevant unitary for those outcomes. - """ - if isinstance(bitstring, list) and isinstance(unitary, list): - assert len(bitstring) == len(unitary) - self.bitstrings += bitstring - self.unitaries += unitary - elif isinstance(bitstring, str) and isinstance(unitary, str): - self.bitstrings.append(bitstring) - self.unitaries.append(unitary) - else: - raise ValueError("bistring and unitary arguments must be consistent strings or list of strings.") - - def get_observable(self, qubit_op, *args, **kwargs): - """Getting an estimated observable value for a qubit operator from the - classical shadow. This function loops through all terms and calls, for - each of them, the get_term_observable method defined in the child class. - Other arguments (args, kwargs) can be passed to the method. - - Args: - qubit_op (QubitOperator): Operator to estimate. - """ - observable = 0. - for term, coeff in qubit_op.terms.items(): - observable += self.get_term_observable(term, coeff, *args, **kwargs) - - return observable - - def simulate(self, backend, initial_statevector=None): - """Simulate, using a predefined backend, a shadow from a circuit or a - statevector. - - Args: - backend (Simulator): Backend for the simulation of a shadow. - initial_statevector(list/array) : A valid statevector in the format - supported by the target backend. - """ - - if not self.unitaries: - raise ValueError(f"The build method of {self.__class__.__name__} must be called before simulation.") - - if backend.n_shots != 1: - warnings.warn(f"Changing number of shots to 1 for the backend (classical shadows).") - backend.n_shots = 1 - - # Different behavior if circuit or initial_statevector is defined. - one_shot_circuit_template = self.circuit if self.circuit is not None else Circuit(n_qubits=self.n_qubits) - - for basis_circuit in self.get_basis_circuits(only_unique=False): - one_shot_circuit = one_shot_circuit_template + basis_circuit if (basis_circuit.size > 0) else one_shot_circuit_template - - # Frequencies returned by simulate are of the form {'0100...01': 1.0}. - # We add the bitstring to the shadow. - freqs, _ = backend.simulate(one_shot_circuit, initial_statevector=initial_statevector) - self.bitstrings += [list(freqs.keys())[0]] - - @abc.abstractmethod - def build(self): - pass - - @abc.abstractmethod - def get_basis_circuits(self, only_unique=False): - pass - - @abc.abstractmethod - def estimate_state(self): - pass - - @abc.abstractmethod - def get_term_observable(self): - pass - - class RandomizedClassicalShadow(ClassicalShadow): r"""Classical shadows using randomized single Pauli measurements, as defined in H.Y. Huang, R. Kueng, and J. Preskill, Nature Physics 16, 1050 (2020). In @@ -222,7 +96,6 @@ def get_basis_circuits(self, only_unique=False): if only_unique: unique_basis_circuits = [(basis_circuits[i], self.unitaries.count(u)) for i, u in enumerate(unitaries_to_convert)] return unique_basis_circuits - # Not necessary if we output everything. else: return basis_circuits @@ -306,8 +179,8 @@ def get_term_observable(self, term, coeff=1., k=10): for i_qubit in range(self.n_qubits): # If there is an operation applied on the qubit n. if i_qubit in dict_term.keys(): - obs = matrices[dict_term[i_qubit]] - tobs = traces[dict_term[i_qubit]] + obs = pauli_matrices[dict_term[i_qubit]] + tobs = pauli_traces[dict_term[i_qubit]] else: # Trace of identity matrix obs = I diff --git a/tangelo/toolboxes/measurements/tests/test_adaptive_classical_shadows.py b/tangelo/toolboxes/measurements/tests/test_adaptive_classical_shadows.py new file mode 100644 index 000000000..de1fab490 --- /dev/null +++ b/tangelo/toolboxes/measurements/tests/test_adaptive_classical_shadows.py @@ -0,0 +1,95 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest + +from tangelo.linq import Gate, Circuit +from tangelo.toolboxes.measurements import AdaptiveClassicalShadow +from tangelo.toolboxes.operators import QubitOperator + +# Circuit to sample (Bell state). +state = Circuit([Gate("H", 0), Gate("CNOT", 1, 0)]) + +# Simple saved bistrings and unitaries to construct a shadow (size = 100). +bitstrings = ["10", "00", "11", "00", "00", "11", "10", "11", "00", "00", "11", + "00", "00", "01", "00", "11", "11", "00", "11", "11", "11", "00", + "11", "11", "01", "00", "11", "01", "11", "11", "01", "10", "00", + "11", "11", "11", "11", "11", "10", "01", "00", "00", "11", "01", + "00", "11", "11", "00", "11", "11", "11", "00", "11", "10", "11", + "11", "00", "11", "10", "01", "11", "10", "10", "01", "00", "10", + "10", "11", "11", "00", "10", "11", "00", "11", "01", "01", "01", + "11", "10", "00", "00", "11", "00", "01", "10", "01", "00", "01", + "00", "01", "11", "00", "00", "00", "10", "11", "00", "11", "10", + "11"] +unitaries = ["YY", "XX", "ZZ", "XX", "ZZ", "ZZ", "YY", "XX", "XX", "XX", "ZZ", + "ZZ", "XX", "YY", "ZZ", "ZZ", "XX", "XX", "ZZ", "XX", "ZZ", "ZZ", + "XX", "ZZ", "YY", "XX", "ZZ", "YY", "XX", "XX", "YY", "YY", "ZZ", + "XX", "XX", "ZZ", "ZZ", "ZZ", "YY", "YY", "ZZ", "XX", "XX", "YY", + "XX", "XX", "XX", "ZZ", "XX", "XX", "XX", "ZZ", "XX", "YY", "ZZ", + "ZZ", "XX", "ZZ", "YY", "YY", "XX", "YY", "YY", "YY", "XX", "YY", + "YY", "XX", "XX", "ZZ", "YY", "ZZ", "ZZ", "ZZ", "YY", "YY", "YY", + "ZZ", "YY", "ZZ", "ZZ", "XX", "ZZ", "YY", "YY", "YY", "XX", "YY", + "ZZ", "YY", "ZZ", "XX", "XX", "XX", "YY", "XX", "ZZ", "XX", "YY", + "XX"] + + +class AdaptiveClassicalShadowTest(unittest.TestCase): + + def test_initialization(self): + """Testing the initialization.""" + + AdaptiveClassicalShadow(state, bitstrings, unitaries) + + def test_shadow_properties(self): + """Testing of the shadow properties.""" + + cs = AdaptiveClassicalShadow(state, bitstrings, unitaries) + + self.assertEqual(cs.n_qubits, 2) + self.assertEqual(cs.size, 100) + self.assertEqual(len(cs), 100) + + def test_get_term_observable(self): + """Testing the computation of a single qubit term.""" + + cs = AdaptiveClassicalShadow(state, bitstrings, unitaries) + + obs_xx = cs.get_term_observable([(0, "X"), (1, "X")], 1.) + self.assertAlmostEqual(obs_xx, 1.0, places=4) + + obs_yy = cs.get_term_observable([(0, "Y"), (1, "Y")], 1.) + self.assertAlmostEqual(obs_yy, -1.0, places=4) + + def test_get_observable(self): + """Testings the computation of an eigenvalue of a QubitOperator.""" + + cs = AdaptiveClassicalShadow(state, bitstrings, unitaries) + obs_xx = cs.get_observable(QubitOperator("X0 X1", coefficient=1.)) + self.assertAlmostEqual(obs_xx, 1.0, places=4) + + obs_yy = cs.get_observable(QubitOperator("Y0 Y1", coefficient=1.)) + self.assertAlmostEqual(obs_yy, -1.0, places=4) + + def test_get_basis_circuits(self): + """Testing of the method to get the appended circuit corresponding to + the unitaries. + """ + cs = AdaptiveClassicalShadow(state, bitstrings, unitaries) + + self.assertEqual(len(cs.get_basis_circuits(False)), 100) + self.assertEqual(len(cs.get_basis_circuits(True)), 3) + + +if __name__ == "__main__": + unittest.main() diff --git a/tangelo/toolboxes/measurements/tests/test_derandomized_classical_shadows.py b/tangelo/toolboxes/measurements/tests/test_derandomized_classical_shadows.py new file mode 100644 index 000000000..82fd7334e --- /dev/null +++ b/tangelo/toolboxes/measurements/tests/test_derandomized_classical_shadows.py @@ -0,0 +1,86 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest + +from tangelo.linq import Gate, Circuit +from tangelo.toolboxes.measurements import DerandomizedClassicalShadow +from tangelo.toolboxes.operators import QubitOperator + +# Circuit to sample (Bell state). +state = Circuit([Gate("H", 0), Gate("CNOT", 1, 0)]) + +# Simple saved bistrings and unitaries to construct a shadow (size = 100). +bitstrings = ["11", "01", "11", "01", "00", "10", "00", "10", "11", "01", "00", + "01", "00", "01", "11", "10", "00", "01", "11", "01", "00", "01", + "11", "01", "11", "01", "00", "01", "11", "10", "11", "10", "11", + "10", "00", "10", "00", "10", "00", "01", "11", "10", "11", "10", + "00", "01", "11", "01", "11", "10", "00", "10", "00", "01", "11", + "10", "00", "10", "11", "10", "11", "10", "00", "01", "11", "01", + "11", "01", "11", "10", "00", "01", "11", "01", "11", "10", "11", + "01", "11", "10", "11", "10", "00", "10", "11", "01", "00", "10", + "00", "10", "11", "10", "11", "01", "11", "01", "00", "10", "11", + "10"] +unitaries = ["XX", "YY"] * 50 + + +class DerandomizedClassicalShadowTest(unittest.TestCase): + + def test_initialization(self): + """Testing the initialization.""" + + DerandomizedClassicalShadow(state, bitstrings, unitaries) + + def test_shadow_properties(self): + """Testing of the shadow properties.""" + + cs = DerandomizedClassicalShadow(state, bitstrings, unitaries) + + self.assertEqual(cs.n_qubits, 2) + self.assertEqual(cs.size, 100) + self.assertEqual(len(cs), 100) + + def test_get_term_observable(self): + """Testing the computation of a single qubit term.""" + + cs = DerandomizedClassicalShadow(state, bitstrings, unitaries) + + obs_xx = cs.get_term_observable([(0, "X"), (1, "X")], 1.) + self.assertAlmostEqual(obs_xx, 1.0, places=4) + + obs_yy = cs.get_term_observable([(0, "Y"), (1, "Y")], 1.) + self.assertAlmostEqual(obs_yy, -1.0, places=4) + + def test_get_observable(self): + """Testings the computation of an eigenvalue of a QubitOperator.""" + + cs = DerandomizedClassicalShadow(state, bitstrings, unitaries) + obs_xx = cs.get_observable(QubitOperator("X0 X1", coefficient=1.)) + self.assertAlmostEqual(obs_xx, 1.0, places=4) + + obs_yy = cs.get_observable(QubitOperator("Y0 Y1", coefficient=1.)) + self.assertAlmostEqual(obs_yy, -1.0, places=4) + + def test_get_basis_circuits(self): + """Testing of the method to get the appended circuit corresponding to + the unitaries. + """ + cs = DerandomizedClassicalShadow(state, bitstrings, unitaries) + + self.assertEqual(len(cs.get_basis_circuits(False)), 100) + self.assertEqual(len(cs.get_basis_circuits(True)), 2) + + +if __name__ == "__main__": + unittest.main() diff --git a/tangelo/toolboxes/measurements/tests/test_classical_shadows.py b/tangelo/toolboxes/measurements/tests/test_randomized_classical_shadows.py similarity index 93% rename from tangelo/toolboxes/measurements/tests/test_classical_shadows.py rename to tangelo/toolboxes/measurements/tests/test_randomized_classical_shadows.py index a8eb097e9..00932cc91 100644 --- a/tangelo/toolboxes/measurements/tests/test_classical_shadows.py +++ b/tangelo/toolboxes/measurements/tests/test_randomized_classical_shadows.py @@ -15,11 +15,10 @@ import unittest import numpy as np -from qiskit.providers.aer.noise.errors.standard_errors import thermal_relaxation_error -from tangelo.linq import Gate, Circuit, Simulator -from tangelo.toolboxes.measurements.classical_shadows import RandomizedClassicalShadow -from tangelo.toolboxes.operators.operators import QubitOperator +from tangelo.linq import Gate, Circuit +from tangelo.toolboxes.measurements import RandomizedClassicalShadow +from tangelo.toolboxes.operators import QubitOperator # Circuit to sample (Bell state). state = Circuit([Gate("H", 0), Gate("CNOT", 1, 0)]) From 06d28b4b32c491071f5c97adefc4827e6a06c38e Mon Sep 17 00:00:00 2001 From: JamesB-1qbit <84878946+JamesB-1qbit@users.noreply.github.com> Date: Mon, 17 Jan 2022 18:40:41 -0500 Subject: [PATCH 55/68] added vsqs ansatz (#109) * added vsqs ansatz Co-authored-by: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> --- .github/workflows/continuous_integration.yml | 2 +- .../variational/tests/test_vqe_solver.py | 25 ++ tangelo/algorithms/variational/vqe_solver.py | 16 +- .../toolboxes/ansatz_generator/__init__.py | 10 + .../ansatz_generator/ansatz_utils.py | 14 +- ...doublecation_minao_init_qubitham_jw_b.data | 18 ++ ...l_H4_doublecation_minao_qubitham_jw_b.data | 186 +++++++++++++++ .../ansatz_generator/tests/test_vsqs.py | 116 +++++++++ tangelo/toolboxes/ansatz_generator/vsqs.py | 223 ++++++++++++++++++ .../molecular_computation/molecule.py | 53 +++-- 10 files changed, 633 insertions(+), 30 deletions(-) create mode 100644 tangelo/toolboxes/ansatz_generator/tests/data/mol_H4_doublecation_minao_init_qubitham_jw_b.data create mode 100644 tangelo/toolboxes/ansatz_generator/tests/data/mol_H4_doublecation_minao_qubitham_jw_b.data create mode 100644 tangelo/toolboxes/ansatz_generator/tests/test_vsqs.py create mode 100644 tangelo/toolboxes/ansatz_generator/vsqs.py diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index d0edfdbe6..ff7e92122 100755 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -40,7 +40,7 @@ jobs: pip install qiskit==0.33.1 # Due to strange behaviour of noise model pip install qulacs pip install amazon-braket-sdk - pip install cirq==0.12.0 + pip install cirq pip install projectq if: always() diff --git a/tangelo/algorithms/variational/tests/test_vqe_solver.py b/tangelo/algorithms/variational/tests/test_vqe_solver.py index c3aaedc09..eefabdc4f 100644 --- a/tangelo/algorithms/variational/tests/test_vqe_solver.py +++ b/tangelo/algorithms/variational/tests/test_vqe_solver.py @@ -137,6 +137,31 @@ def test_simulate_qcc_h2(self): energy = vqe_solver.simulate() self.assertAlmostEqual(energy, -1.137270, delta=1e-4) + def test_simulate_vsqs_h2(self): + """Run VQE on H2 molecule, with vsqs ansatz, JW qubit mapping, exact simulator for both molecule input and + qubit_hamiltonian/hini/reference_state input + """ + vqe_options = {"molecule": mol_H2_sto3g, "ansatz": BuiltInAnsatze.VSQS, "qubit_mapping": "jw", + "verbose": False, "ansatz_options": {"intervals": 3, "time": 3}} + vqe_solver = VQESolver(vqe_options) + vqe_solver.build() + + energy = vqe_solver.simulate() + self.assertAlmostEqual(energy, -1.137270, delta=1e-4) + + qubit_hamiltonian = vqe_solver.qubit_hamiltonian + h_init = vqe_solver.ansatz.h_init + reference_state = vqe_solver.ansatz.prepare_reference_state() + + vqe_options = {"molecule": None, "qubit_hamiltonian": qubit_hamiltonian, "ansatz": BuiltInAnsatze.VSQS, "qubit_mapping": "jw", + "ansatz_options": {"intervals": 3, "time": 3, "qubit_hamiltonian": qubit_hamiltonian, + "h_init": h_init, "reference_state": reference_state}} + vqe_solver = VQESolver(vqe_options) + vqe_solver.build() + + energy = vqe_solver.simulate() + self.assertAlmostEqual(energy, -1.137270, delta=1e-4) + def test_simulate_h2_qiskit(self): """Run VQE on H2 molecule, with UCCSD ansatz, JW qubit mapping, initial parameters, exact qiskit simulator. diff --git a/tangelo/algorithms/variational/vqe_solver.py b/tangelo/algorithms/variational/vqe_solver.py index 639e3bf3c..6f8180696 100644 --- a/tangelo/algorithms/variational/vqe_solver.py +++ b/tangelo/algorithms/variational/vqe_solver.py @@ -29,13 +29,7 @@ from tangelo.toolboxes.operators import count_qubits, FermionOperator, qubitop_to_qubitham from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping from tangelo.toolboxes.ansatz_generator.ansatz import Ansatz -from tangelo.toolboxes.ansatz_generator.uccsd import UCCSD -from tangelo.toolboxes.ansatz_generator.rucc import RUCC -from tangelo.toolboxes.ansatz_generator.hea import HEA -from tangelo.toolboxes.ansatz_generator.upccgsd import UpCCGSD -from tangelo.toolboxes.ansatz_generator.qmf import QMF -from tangelo.toolboxes.ansatz_generator.qcc import QCC -from tangelo.toolboxes.ansatz_generator.variational_circuit import VariationalCircuitAnsatz +from tangelo.toolboxes.ansatz_generator import UCCSD, RUCC, HEA, UpCCGSD, QMF, QCC, VSQS, VariationalCircuitAnsatz from tangelo.toolboxes.ansatz_generator.penalty_terms import combined_penalty from tangelo.toolboxes.post_processing.bootstrapping import get_resampled_frequencies from tangelo.toolboxes.ansatz_generator.fermionic_operators import number_operator, spinz_operator, spin2_operator @@ -50,6 +44,7 @@ class BuiltInAnsatze(Enum): UpCCGSD = 4 QMF = 5 QCC = 6 + VSQS = 7 class VQESolver: @@ -186,12 +181,19 @@ def build(self): self.ansatz = QMF(self.molecule, self.qubit_mapping, self.up_then_down, **self.ansatz_options) elif self.ansatz == BuiltInAnsatze.QCC: self.ansatz = QCC(self.molecule, self.qubit_mapping, self.up_then_down, **self.ansatz_options) + elif self.ansatz == BuiltInAnsatze.VSQS: + self.ansatz = VSQS(self.molecule, self.qubit_mapping, self.up_then_down, **self.ansatz_options) else: raise ValueError(f"Unsupported ansatz. Built-in ansatze:\n\t{self.builtin_ansatze}") elif not isinstance(self.ansatz, Ansatz): raise TypeError(f"Invalid ansatz dataype. Expecting instance of Ansatz class, or one of built-in options:\n\t{self.builtin_ansatze}") # Building with a qubit Hamiltonian. + elif self.ansatz in [BuiltInAnsatze.HEA, BuiltInAnsatze.VSQS]: + if self.ansatz == BuiltInAnsatze.HEA: + self.ansatz = HEA(self.molecule, self.qubit_mapping, self.up_then_down, **self.ansatz_options) + elif self.ansatz == BuiltInAnsatze.VSQS: + self.ansatz = VSQS(self.molecule, self.qubit_mapping, self.up_then_down, **self.ansatz_options) elif not isinstance(self.ansatz, Ansatz): raise TypeError(f"Invalid ansatz dataype. Expecting a custom Ansatz (Ansatz class).") diff --git a/tangelo/toolboxes/ansatz_generator/__init__.py b/tangelo/toolboxes/ansatz_generator/__init__.py index 532746351..1940ada44 100644 --- a/tangelo/toolboxes/ansatz_generator/__init__.py +++ b/tangelo/toolboxes/ansatz_generator/__init__.py @@ -11,3 +11,13 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + +from .vsqs import VSQS +from .qcc import QCC +from .qmf import QMF +from .uccsd import UCCSD +from .adapt_ansatz import ADAPTAnsatz +from .rucc import RUCC +from .upccgsd import UpCCGSD +from .hea import HEA +from .variational_circuit import VariationalCircuitAnsatz diff --git a/tangelo/toolboxes/ansatz_generator/ansatz_utils.py b/tangelo/toolboxes/ansatz_generator/ansatz_utils.py index a9a2a4c16..825f7f587 100644 --- a/tangelo/toolboxes/ansatz_generator/ansatz_utils.py +++ b/tangelo/toolboxes/ansatz_generator/ansatz_utils.py @@ -83,7 +83,7 @@ def exp_pauliword_to_gates(pauli_word, coef, variational=True, control=None): def get_exponentiated_qubit_operator_circuit(qubit_op, time=1., variational=False, trotter_order=1, control=None, - return_phase=False): + return_phase=False, pauli_order=None): """Generate the exponentiation of a qubit operator in first- or second-order Trotterized form. The algorithm is described in Whitfield 2010 https://arxiv.org/pdf/1001.3855.pdf @@ -94,12 +94,20 @@ def get_exponentiated_qubit_operator_circuit(qubit_op, time=1., variational=Fals variational (bool) : Whether the coefficients are variational trotter_order (int): order of trotter approximation, only 1 or 2 are supported. return_phase (bool): Return the global-phase generated + pauli_order (list): The desired pauli_word order for trotterization defined as a list of (pauli_word, coeff) + elements which have matching dictionary elements pauli_word: coeff in QubitOperator terms.items(). + The coeff in pauli_order is used to generate the exponential. Returns: Circuit: circuit corresponding to exponentiation of qubit operator phase : The global phase of the time evolution if return_phase=True else not included """ - pauli_words = list(qubit_op.terms.items()) + if pauli_order is None: + pauli_words = list(qubit_op.terms.items()) + elif isinstance(pauli_order, list): + pauli_words = pauli_order.copy() + else: + raise ValueError("ordered terms must be a list with elements (keys, values) of qubit_op.terms.items()") if trotter_order > 2: raise ValueError(f"Trotter order of >2 is not supported currently in Tangelo.") @@ -118,7 +126,7 @@ def get_exponentiated_qubit_operator_circuit(qubit_op, time=1., variational=Fals phase = 1. exp_pauli_word_gates = list() for i in range(trotter_order): - if i == 0: + if i == 1: pauli_words.reverse() for pauli_word, coef in pauli_words: if pauli_word: # identity terms do not contribute to evolution outside of a phase diff --git a/tangelo/toolboxes/ansatz_generator/tests/data/mol_H4_doublecation_minao_init_qubitham_jw_b.data b/tangelo/toolboxes/ansatz_generator/tests/data/mol_H4_doublecation_minao_init_qubitham_jw_b.data new file mode 100644 index 000000000..3de6e4b08 --- /dev/null +++ b/tangelo/toolboxes/ansatz_generator/tests/data/mol_H4_doublecation_minao_init_qubitham_jw_b.data @@ -0,0 +1,18 @@ +QubitOperator: +(-3.201203136341336+0j) [] + +(0.018922220448748025+0j) [X0 X1] + +(0.018922220448748025+0j) [Y0 Y1] + +(0.6887270305253504+0j) [Z0] + +(0.37691668685924784+0j) [Z1] + +(-0.02281117974146273+0j) [X2 X3] + +(-0.02281117974146273+0j) [Y2 Y3] + +(0.3753113309246301+0j) [Z2] + +(0.15964651986143952+0j) [Z3] + +(0.018922220448748025+0j) [X4 X5] + +(0.018922220448748025+0j) [Y4 Y5] + +(0.6887270305253504+0j) [Z4] + +(0.37691668685924784+0j) [Z5] + +(-0.02281117974146273+0j) [X6 X7] + +(-0.02281117974146273+0j) [Y6 Y7] + +(0.3753113309246301+0j) [Z6] + +(0.15964651986143952+0j) [Z7] \ No newline at end of file diff --git a/tangelo/toolboxes/ansatz_generator/tests/data/mol_H4_doublecation_minao_qubitham_jw_b.data b/tangelo/toolboxes/ansatz_generator/tests/data/mol_H4_doublecation_minao_qubitham_jw_b.data new file mode 100644 index 000000000..a602becad --- /dev/null +++ b/tangelo/toolboxes/ansatz_generator/tests/data/mol_H4_doublecation_minao_qubitham_jw_b.data @@ -0,0 +1,186 @@ +QubitOperator: +(-0.845088084959612+0j) [] + +(-0.0020776113200212075+0j) [X0 X1] + +(-0.011761015389060145+0j) [X0 X1 X2 X3] + +(-0.0003365342989052316+0j) [X0 X1 Y2 Y3] + +(0.002844217831601514+0j) [X0 X1 Z2] + +(0.005067140284950715+0j) [X0 X1 Z3] + +(0.02660445410908035+0j) [X0 X1 X4 X5] + +(0.02660445410908035+0j) [X0 X1 Y4 Y5] + +(0.009461086978084416+0j) [X0 X1 Z4] + +(-0.002045316086697597+0j) [X0 X1 Z5] + +(-0.024395190370696845+0j) [X0 X1 X6 X7] + +(-0.024395190370696845+0j) [X0 X1 Y6 Y7] + +(-0.007801932778402895+0j) [X0 X1 Z6] + +(0.013474635539233077+0j) [X0 X1 Z7] + +(-0.011424481090154913+0j) [X0 Y1 Y2 X3] + +(0.024934767638066777+0j) [X0 Z1 X2 X4 Z5 X6] + +(0.006238164536781785+0j) [X0 Z1 X2 X4 Z5 Z6 X7] + +(0.024934767638066777+0j) [X0 Z1 X2 Y4 Z5 Y6] + +(0.006238164536781785+0j) [X0 Z1 X2 Y4 Z5 Z6 Y7] + +(0.01064615061000441+0j) [X0 Z1 X2 X5 X6] + +(-0.02405865607179162+0j) [X0 Z1 X2 X5 Z6 X7] + +(0.01064615061000441+0j) [X0 Z1 X2 Y5 Y6] + +(-0.02405865607179162+0j) [X0 Z1 X2 Y5 Z6 Y7] + +(0.006238164536781785+0j) [X0 Z1 Z2 X3 X4 Z5 X6] + +(0.017676042848046+0j) [X0 Z1 Z2 X3 X4 Z5 Z6 X7] + +(0.006238164536781785+0j) [X0 Z1 Z2 X3 Y4 Z5 Y6] + +(0.017676042848046+0j) [X0 Z1 Z2 X3 Y4 Z5 Z6 Y7] + +(-0.012634174981636702+0j) [X0 Z1 Z2 X3 X5 X6] + +(-0.008407495254282365+0j) [X0 Z1 Z2 X3 X5 Z6 X7] + +(-0.012634174981636702+0j) [X0 Z1 Z2 X3 Y5 Y6] + +(-0.008407495254282365+0j) [X0 Z1 Z2 X3 Y5 Z6 Y7] + +(-0.011424481090154913+0j) [Y0 X1 X2 Y3] + +(-0.0020776113200212075+0j) [Y0 Y1] + +(-0.0003365342989052316+0j) [Y0 Y1 X2 X3] + +(-0.011761015389060145+0j) [Y0 Y1 Y2 Y3] + +(0.002844217831601514+0j) [Y0 Y1 Z2] + +(0.005067140284950715+0j) [Y0 Y1 Z3] + +(0.02660445410908035+0j) [Y0 Y1 X4 X5] + +(0.02660445410908035+0j) [Y0 Y1 Y4 Y5] + +(0.009461086978084416+0j) [Y0 Y1 Z4] + +(-0.002045316086697597+0j) [Y0 Y1 Z5] + +(-0.024395190370696845+0j) [Y0 Y1 X6 X7] + +(-0.024395190370696845+0j) [Y0 Y1 Y6 Y7] + +(-0.007801932778402895+0j) [Y0 Y1 Z6] + +(0.013474635539233077+0j) [Y0 Y1 Z7] + +(0.024934767638066777+0j) [Y0 Z1 Y2 X4 Z5 X6] + +(0.006238164536781785+0j) [Y0 Z1 Y2 X4 Z5 Z6 X7] + +(0.024934767638066777+0j) [Y0 Z1 Y2 Y4 Z5 Y6] + +(0.006238164536781785+0j) [Y0 Z1 Y2 Y4 Z5 Z6 Y7] + +(0.01064615061000441+0j) [Y0 Z1 Y2 X5 X6] + +(-0.02405865607179162+0j) [Y0 Z1 Y2 X5 Z6 X7] + +(0.01064615061000441+0j) [Y0 Z1 Y2 Y5 Y6] + +(-0.02405865607179162+0j) [Y0 Z1 Y2 Y5 Z6 Y7] + +(0.006238164536781785+0j) [Y0 Z1 Z2 Y3 X4 Z5 X6] + +(0.017676042848046+0j) [Y0 Z1 Z2 Y3 X4 Z5 Z6 X7] + +(0.006238164536781785+0j) [Y0 Z1 Z2 Y3 Y4 Z5 Y6] + +(0.017676042848046+0j) [Y0 Z1 Z2 Y3 Y4 Z5 Z6 Y7] + +(-0.012634174981636702+0j) [Y0 Z1 Z2 Y3 X5 X6] + +(-0.008407495254282365+0j) [Y0 Z1 Z2 Y3 X5 Z6 X7] + +(-0.012634174981636702+0j) [Y0 Z1 Z2 Y3 Y5 Y6] + +(-0.008407495254282365+0j) [Y0 Z1 Z2 Y3 Y5 Z6 Y7] + +(0.23477912416223978+0j) [Z0] + +(0.07755867751288839+0j) [Z0 Z1] + +(-0.0025837417313018998+0j) [Z0 X2 X3] + +(-0.0025837417313018998+0j) [Z0 Y2 Y3] + +(0.07733247653566544+0j) [Z0 Z2] + +(0.09333330628587891+0j) [Z0 Z3] + +(0.009461086978084416+0j) [Z0 X4 X5] + +(0.009461086978084416+0j) [Z0 Y4 Y5] + +(0.11171627890094822+0j) [Z0 Z4] + +(0.10416313162196875+0j) [Z0 Z5] + +(-0.008821906268083685+0j) [Z0 X6 X7] + +(-0.008821906268083685+0j) [Z0 Y6 Y7] + +(0.10226724417373223+0j) [Z0 Z6] + +(0.1110093491339249+0j) [Z0 Z7] + +(0.010646150610004408+0j) [X1 X2 X4 Z5 X6] + +(-0.012634174981636702+0j) [X1 X2 X4 Z5 Z6 X7] + +(0.010646150610004408+0j) [X1 X2 Y4 Z5 Y6] + +(-0.012634174981636702+0j) [X1 X2 Y4 Z5 Z6 Y7] + +(0.01934883030685017+0j) [X1 X2 X5 X6] + +(-0.008901638567159004+0j) [X1 X2 X5 Z6 X7] + +(0.01934883030685017+0j) [X1 X2 Y5 Y6] + +(-0.008901638567159004+0j) [X1 X2 Y5 Z6 Y7] + +(-0.02405865607179162+0j) [X1 Z2 X3 X4 Z5 X6] + +(-0.008407495254282365+0j) [X1 Z2 X3 X4 Z5 Z6 X7] + +(-0.02405865607179162+0j) [X1 Z2 X3 Y4 Z5 Y6] + +(-0.008407495254282365+0j) [X1 Z2 X3 Y4 Z5 Z6 Y7] + +(-0.008901638567159004+0j) [X1 Z2 X3 X5 X6] + +(0.02661467635818076+0j) [X1 Z2 X3 X5 Z6 X7] + +(-0.008901638567159004+0j) [X1 Z2 X3 Y5 Y6] + +(0.02661467635818076+0j) [X1 Z2 X3 Y5 Z6 Y7] + +(0.010646150610004408+0j) [Y1 Y2 X4 Z5 X6] + +(-0.012634174981636702+0j) [Y1 Y2 X4 Z5 Z6 X7] + +(0.010646150610004408+0j) [Y1 Y2 Y4 Z5 Y6] + +(-0.012634174981636702+0j) [Y1 Y2 Y4 Z5 Z6 Y7] + +(0.01934883030685017+0j) [Y1 Y2 X5 X6] + +(-0.008901638567159004+0j) [Y1 Y2 X5 Z6 X7] + +(0.01934883030685017+0j) [Y1 Y2 Y5 Y6] + +(-0.008901638567159004+0j) [Y1 Y2 Y5 Z6 Y7] + +(-0.02405865607179162+0j) [Y1 Z2 Y3 X4 Z5 X6] + +(-0.008407495254282365+0j) [Y1 Z2 Y3 X4 Z5 Z6 X7] + +(-0.02405865607179162+0j) [Y1 Z2 Y3 Y4 Z5 Y6] + +(-0.008407495254282365+0j) [Y1 Z2 Y3 Y4 Z5 Z6 Y7] + +(-0.008901638567159004+0j) [Y1 Z2 Y3 X5 X6] + +(0.02661467635818076+0j) [Y1 Z2 Y3 X5 Z6 X7] + +(-0.008901638567159004+0j) [Y1 Z2 Y3 Y5 Y6] + +(0.02661467635818076+0j) [Y1 Z2 Y3 Y5 Z6 Y7] + +(0.07873792608304384+0j) [Z1] + +(-0.00523830590529105+0j) [Z1 X2 X3] + +(-0.00523830590529105+0j) [Z1 Y2 Y3] + +(0.08417436673290517+0j) [Z1 Z2] + +(0.07928157442541509+0j) [Z1 Z3] + +(-0.002045316086697597+0j) [Z1 X4 X5] + +(-0.002045316086697597+0j) [Z1 Y4 Y5] + +(0.10416313162196875+0j) [Z1 Z4] + +(0.10702518092938991+0j) [Z1 Z5] + +(0.0036633326618679546+0j) [Z1 X6 X7] + +(0.0036633326618679546+0j) [Z1 Y6 Y7] + +(0.10352319703975532+0j) [Z1 Z6] + +(0.10589625078359582+0j) [Z1 Z7] + +(-0.004483252597706383+0j) [X2 X3] + +(-0.024395190370696845+0j) [X2 X3 X4 X5] + +(-0.024395190370696845+0j) [X2 X3 Y4 Y5] + +(-0.008821906268083685+0j) [X2 X3 Z4] + +(0.0036633326618679546+0j) [X2 X3 Z5] + +(0.025689949571830042+0j) [X2 X3 X6 X7] + +(0.025689949571830042+0j) [X2 X3 Y6 Y7] + +(0.007649625311503733+0j) [X2 X3 Z6] + +(-0.012996931212451403+0j) [X2 X3 Z7] + +(-0.004483252597706383+0j) [Y2 Y3] + +(-0.024395190370696845+0j) [Y2 Y3 X4 X5] + +(-0.024395190370696845+0j) [Y2 Y3 Y4 Y5] + +(-0.008821906268083685+0j) [Y2 Y3 Z4] + +(0.0036633326618679546+0j) [Y2 Y3 Z5] + +(0.025689949571830042+0j) [Y2 Y3 X6 X7] + +(0.025689949571830042+0j) [Y2 Y3 Y6 Y7] + +(0.007649625311503733+0j) [Y2 Y3 Z6] + +(-0.012996931212451403+0j) [Y2 Y3 Z7] + +(0.07666866090982398+0j) [Z2] + +(0.07784794028451397+0j) [Z2 Z3] + +(-0.007801932778402895+0j) [Z2 X4 X5] + +(-0.007801932778402895+0j) [Z2 Y4 Y5] + +(0.10226724417373223+0j) [Z2 Z4] + +(0.10352319703975532+0j) [Z2 Z5] + +(0.007649625311503733+0j) [Z2 X6 X7] + +(0.007649625311503733+0j) [Z2 Y6 Y7] + +(0.1091589968106852+0j) [Z2 Z6] + +(0.10353788985634402+0j) [Z2 Z7] + +(-0.12227609382406271+0j) [Z3] + +(0.013474635539233077+0j) [Z3 X4 X5] + +(0.013474635539233077+0j) [Z3 Y4 Y5] + +(0.1110093491339249+0j) [Z3 Z4] + +(0.10589625078359582+0j) [Z3 Z5] + +(-0.012996931212451403+0j) [Z3 X6 X7] + +(-0.012996931212451403+0j) [Z3 Y6 Y7] + +(0.10353788985634402+0j) [Z3 Z6] + +(0.11970161375543707+0j) [Z3 Z7] + +(-0.0020776113200212066+0j) [X4 X5] + +(-0.011761015389060145+0j) [X4 X5 X6 X7] + +(-0.0003365342989052316+0j) [X4 X5 Y6 Y7] + +(0.002844217831601514+0j) [X4 X5 Z6] + +(0.005067140284950715+0j) [X4 X5 Z7] + +(-0.011424481090154913+0j) [X4 Y5 Y6 X7] + +(-0.011424481090154913+0j) [Y4 X5 X6 Y7] + +(-0.0020776113200212066+0j) [Y4 Y5] + +(-0.0003365342989052316+0j) [Y4 Y5 X6 X7] + +(-0.011761015389060145+0j) [Y4 Y5 Y6 Y7] + +(0.002844217831601514+0j) [Y4 Y5 Z6] + +(0.005067140284950715+0j) [Y4 Y5 Z7] + +(0.23477912416223978+0j) [Z4] + +(0.07755867751288839+0j) [Z4 Z5] + +(-0.0025837417313018998+0j) [Z4 X6 X7] + +(-0.0025837417313018998+0j) [Z4 Y6 Y7] + +(0.07733247653566544+0j) [Z4 Z6] + +(0.09333330628587891+0j) [Z4 Z7] + +(0.07873792608304378+0j) [Z5] + +(-0.00523830590529105+0j) [Z5 X6 X7] + +(-0.00523830590529105+0j) [Z5 Y6 Y7] + +(0.08417436673290517+0j) [Z5 Z6] + +(0.07928157442541509+0j) [Z5 Z7] + +(-0.004483252597706375+0j) [X6 X7] + +(-0.004483252597706375+0j) [Y6 Y7] + +(0.07666866090982404+0j) [Z6] + +(0.07784794028451397+0j) [Z6 Z7] + +(-0.12227609382406274+0j) [Z7] \ No newline at end of file diff --git a/tangelo/toolboxes/ansatz_generator/tests/test_vsqs.py b/tangelo/toolboxes/ansatz_generator/tests/test_vsqs.py new file mode 100644 index 000000000..c1d8985e6 --- /dev/null +++ b/tangelo/toolboxes/ansatz_generator/tests/test_vsqs.py @@ -0,0 +1,116 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest +import os + +import numpy as np +from openfermion import load_operator + +from tangelo.molecule_library import mol_H2_sto3g +from tangelo.toolboxes.operators import QubitOperator +from tangelo.toolboxes.qubit_mappings import jordan_wigner, symmetry_conserving_bravyi_kitaev +from tangelo.toolboxes.ansatz_generator import VSQS +from tangelo.linq import Simulator +from tangelo.toolboxes.qubit_mappings.statevector_mapping import get_reference_circuit + +# For openfermion.load_operator function. +pwd_this_test = os.path.dirname(os.path.abspath(__file__)) + + +class VSQSTest(unittest.TestCase): + + def test_vsqs_set_var_params(self): + """Verify behavior of set_var_params for different inputs (list, numpy array). + """ + + vsqs_ansatz = VSQS(mol_H2_sto3g) + + two_ones = np.ones((2,)) + + vsqs_ansatz.set_var_params([1., 1.]) + np.testing.assert_array_almost_equal(vsqs_ansatz.var_params, two_ones, decimal=6) + + vsqs_ansatz.set_var_params(np.array([1., 1.])) + np.testing.assert_array_almost_equal(vsqs_ansatz.var_params, two_ones, decimal=6) + + def test_vsqs_incorrect_number_var_params(self): + """Return an error if user provide incorrect number of variational + parameters. + """ + + vsqs_ansatz = VSQS(mol_H2_sto3g) + + self.assertRaises(ValueError, vsqs_ansatz.set_var_params, np.array([1., 1., 1., 1.])) + + def test_vsqs_H2(self): + """Verify closed-shell VSQS functionalities for H2.""" + + # Build circuit + vsqs_ansatz = VSQS(mol_H2_sto3g, intervals=3, time=3) + vsqs_ansatz.build_circuit() + + # Build qubit hamiltonian for energy evaluation + qubit_hamiltonian = jordan_wigner(mol_H2_sto3g.fermionic_hamiltonian) + + # Assert energy returned is as expected for given parameters + sim = Simulator() + vsqs_ansatz.update_var_params([0.66666667, 0.9698286, 0.21132472, 0.6465473]) + energy = sim.get_expectation_value(qubit_hamiltonian, vsqs_ansatz.circuit) + self.assertAlmostEqual(energy, -1.1372701255155757, delta=1e-6) + + def test_vsqs_H4_doublecation(self): + """Verify closed-shell VSQS functionalities for H4 2+ by using saved qubit hamiltonian and initial hamiltonian""" + + var_params = [-2.53957674, 0.72683888, 1.08799500, 0.49836183, + -0.23020698, 0.93278630, 0.50591026, 0.50486903] + + # Build qubit hamiltonian for energy evaluation + qubit_hamiltonian = load_operator("mol_H4_doublecation_minao_qubitham_jw_b.data", data_directory=pwd_this_test+"/data", plain_text=True) + initial_hamiltonian = load_operator("mol_H4_doublecation_minao_init_qubitham_jw_b.data", data_directory=pwd_this_test+"/data", plain_text=True) + reference_state = get_reference_circuit(8, 2, "jw", up_then_down=True, spin=0) + + # Build circuit + vsqs_ansatz = VSQS(qubit_hamiltonian=qubit_hamiltonian, h_init=initial_hamiltonian, reference_state=reference_state, + intervals=5, time=5, trotter_order=2) + vsqs_ansatz.build_circuit() + + # Assert energy returned is as expected for given parameters + sim = Simulator() + vsqs_ansatz.update_var_params(var_params) + energy = sim.get_expectation_value(qubit_hamiltonian, vsqs_ansatz.circuit) + self.assertAlmostEqual(energy, -0.85425, delta=1e-4) + + def test_vsqs_H2_with_h_nav(self): + """Verify closed-shell VSQS functionalities for H2 with navigator hamiltonian""" + navigator_hamiltonian = (QubitOperator('X0 Y1', 0.03632537110234512) + QubitOperator('Y0 X1', 0.03632537110234512) + + QubitOperator('Y0', 2.e-5) + QubitOperator('Y1', 2.e-5)) + + # Build qubit hamiltonian for energy evaluation + qubit_hamiltonian = symmetry_conserving_bravyi_kitaev(mol_H2_sto3g.fermionic_hamiltonian, 4, 2, False, 0) + + # Build circuit + vsqs_ansatz = VSQS(mol_H2_sto3g, intervals=2, time=1, mapping='scbk', up_then_down=True, trotter_order=2, + h_nav=navigator_hamiltonian) + vsqs_ansatz.build_circuit() + + # Assert energy returned is as expected for given parameters + sim = Simulator() + vsqs_ansatz.update_var_params([0.50000001, -0.02494214, 3.15398767]) + energy = sim.get_expectation_value(qubit_hamiltonian, vsqs_ansatz.circuit) + self.assertAlmostEqual(energy, -1.1372701255155757, delta=1e-6) + + +if __name__ == "__main__": + unittest.main() diff --git a/tangelo/toolboxes/ansatz_generator/vsqs.py b/tangelo/toolboxes/ansatz_generator/vsqs.py new file mode 100644 index 000000000..708ff8f67 --- /dev/null +++ b/tangelo/toolboxes/ansatz_generator/vsqs.py @@ -0,0 +1,223 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This module defines the Variationally Scheduled Quantum Simulation class. It provides an +Adiabatic State Preparation (ASP) inspired ansatz as described in https://arxiv.org/abs/2003.09913.""" + +import numpy as np +from openfermion import QubitOperator as ofQubitOperator + +from .ansatz import Ansatz +from .ansatz_utils import get_exponentiated_qubit_operator_circuit +from tangelo.toolboxes.operators import FermionOperator +from tangelo.linq import Circuit +from tangelo.toolboxes.qubit_mappings.mapping_transform import get_qubit_number, fermion_to_qubit_mapping +from tangelo.toolboxes.qubit_mappings.statevector_mapping import get_reference_circuit + + +class VSQS(Ansatz): + """This class implements the Variationally Scheduled Quantum Simulator (VSQS) for state preparation as described in + https://arxiv.org/abs/2003.09913 + + Must supply either a molecule or a qubit_hamiltonian. If supplying a qubit_hamiltonian, must also supply + a reference_state Circuit and a h_init QubitOperator. + + Args: + molecule (SecondQuantizedMolecule): The molecular system. Default: None + mapping (str): One of the supported fermion to qubit mappings. Default : "JW" + up_then_down (bool): Change basis ordering, putting all spin up orbitals first, followed by all spin down. + Default: False (alternating spin up/down ordering) + intervals (int): The number of steps in the VSQS process. Must be greater than 1. Default: 2 + time (float): The propagation time. Default: 1. + qubit_hamiltonian (QubitOperator): The qubit Hamiltonian to evolve. Default: None + reference_state (Circuit): The reference state for the propagation as defined by a Circuit. Mandatory if supplying + a qubit_hamiltonian. Default: None + h_init (QubitOperator): The initial qubit Hamiltonian that corresponds to the reference state. Mandatory if supplying + a qubit_hamiltonian. Default: None + h_nav (QubitOperator): The navigator Hamiltonian. Default: None + trotter_order (int): The order of the Trotterization for each qubit operator. Default: 1 + """ + + def __init__(self, molecule=None, mapping="jw", up_then_down=False, intervals=2, time=1., qubit_hamiltonian=None, + reference_state=None, h_init=None, h_nav=None, trotter_order=1): + + self.up_then_down = up_then_down + self.mapping = mapping + if intervals > 1: + self.intervals = intervals + else: + raise ValueError("The number of intervals must be greater than 1.") + self.time = time + self.dt = self.time/self.intervals + self.reference_state = reference_state + if trotter_order in [1, 2]: + self.trotter_order = trotter_order + else: + raise ValueError("Only trotter_order = 1, 2 is supported") + + if molecule is None: + self.qubit_hamiltonian = qubit_hamiltonian + if not isinstance(h_init, ofQubitOperator): + raise ValueError("When providing a qubit hamiltonian, an initial qubit Hamiltonian must also be provided") + self.h_init = h_init + if not isinstance(reference_state, Circuit): + raise ValueError("Reference state Circuit must be provided when simulating a qubit hamiltonian directly") + self.reference_state = reference_state + else: + self.n_electrons = molecule.n_active_electrons + self.n_spinorbitals = int(molecule.n_sos) + self.n_qubits = get_qubit_number(mapping, self.n_spinorbitals) + self.spin = molecule.spin + self.qubit_hamiltonian = fermion_to_qubit_mapping(molecule.fermionic_hamiltonian, n_spinorbitals=self.n_spinorbitals, + n_electrons=self.n_electrons, mapping=self.mapping, + up_then_down=self.up_then_down, spin=self.spin) + self.h_init = self._build_h_init(molecule) if h_init is None else h_init + self.h_final = self.qubit_hamiltonian + self.h_nav = h_nav + + def qu_op_to_list(qu_op): + """Remove consant term and convert QubitOperator to list of (term, coeff)""" + new_qu_op = qu_op - qu_op.constant + new_qu_op.compress() + return list(new_qu_op.terms.items()) + + self.h_final_list = qu_op_to_list(self.h_final) + self.n_h_final = len(self.h_final_list) + self.h_init_list = qu_op_to_list(self.h_init) + self.n_h_init = len(self.h_init_list) + if self.h_nav is None: + self.stride = 2 + self.n_h_nav = 0 + else: + if isinstance(self.h_nav, ofQubitOperator): + self.stride = 3 + self.h_nav_list = qu_op_to_list(self.h_nav) + self.n_h_nav = len(self.h_nav_list) + else: + raise ValueError("Navigator Hamiltonian must be a QubitOperator") + + self.n_var_params = (intervals - 1) * self.stride + self.n_var_gates = (self.n_h_init + self.n_h_final + self.n_h_nav) * self.trotter_order + + self.var_params = None + self.circuit = None + + def _build_h_init(self, molecule): + """Return the initial Hamiltonian (h_init) composed of the one-body terms derived from the diagonal of Fock + matrix and one-body off-diagonal terms""" + core_constant, h1, two_body = molecule.get_active_space_integrals() + diag_fock = np.diag(h1).copy() + n_active_occupied = len(molecule.active_occupied) + for j in range(molecule.n_active_mos): + for i in range(n_active_occupied): + diag_fock[j] += 2*two_body[i, j, j, i] - 1*two_body[i, j, i, j] + + hf_ferm = FermionOperator((), core_constant) + for i in range(molecule.n_active_mos): + for j in range(molecule.n_active_mos): + if i != j: + hf_ferm += FermionOperator(((i * 2, 1), (j * 2, 0)), h1[i, j]) + hf_ferm += FermionOperator(((i * 2 + 1, 1), (j * 2 + 1, 0)), h1[i, j]) + else: + hf_ferm += FermionOperator(((i * 2, 1), (j * 2, 0)), diag_fock[i]) + hf_ferm += FermionOperator(((i * 2 + 1, 1), (j * 2 + 1, 0)), diag_fock[j]) + return fermion_to_qubit_mapping(hf_ferm, mapping=self.mapping, n_spinorbitals=self.n_spinorbitals, n_electrons=self.n_electrons, + up_then_down=self.up_then_down, spin=self.spin) + + def set_var_params(self, var_params=None): + """Set values for the variational parameters. Default is linear interpolation.""" + if var_params is None: + var_params = self._init_params()[self.stride:self.n_var_params+self.stride] + + init_var_params = np.array(var_params) + if init_var_params.size == self.n_var_params: + self.var_params = var_params + else: + raise ValueError(f"Expected {self.n_var_params} variational parameters but received {init_var_params.size}.") + return var_params + + def update_var_params(self, var_params): + """Update the variational parameters in the circuit without rebuilding.""" + for i in range(self.intervals-1): + self._update_gate_params_for_qu_op(self.h_init_list, self.n_var_gates * i, var_params[self.stride*i], self.n_h_init) + self._update_gate_params_for_qu_op(self.h_final_list, self.n_var_gates * i + self.n_h_init * self.trotter_order, + var_params[self.stride*i+1], self.n_h_final) + if self.h_nav is not None: + self._update_gate_params_for_qu_op(self.h_nav_list, self.n_var_gates * i + (self.n_h_init + self.n_h_final) * self.trotter_order, + var_params[self.stride*i+2], self.n_h_nav) + + def _update_gate_params_for_qu_op(self, qu_op_list, n_var_start, var_param, num_terms): + """Updates the corresponding circuit variational_gates for a QubitOperator defined by term order qu_op_list + + Args: + qu_op_list :: The list with elements (term, coeff) that defines the trotterization of a QubitOperator + n_var_start :: The varational_gates position that the trotterization of qu_op starts + var_param :: The variational parameter (evolution time) for qu_op. Same for all terms in qu_op + num_terms :: The number of terms in qu_op + """ + prefac = 2 / self.trotter_order * self.dt * var_param + for i, (_, coeff) in enumerate(qu_op_list): + self.circuit._variational_gates[n_var_start+i].parameter = prefac * coeff + if self.trotter_order == 2: + for i, (_, coeff) in enumerate(list(reversed(qu_op_list))): + self.circuit._variational_gates[n_var_start+num_terms+i].parameter = prefac * coeff + + def _init_params(self): + """Generate initial parameters for the VSQS algorithm. + a_i = step*i, b_i=1-step*i, c_i= 1-step*i i<=n_intervals/2, step*i i>n_intervals/2 + """ + a = np.zeros(self.intervals+1) + b = np.zeros(self.intervals+1) + a[0] = 1 + b[self.intervals] = 1 + step_size = 1/self.intervals + for i in range(1, self.intervals): + a[i] = (1 - i * step_size) + b[i] = (i * step_size) + all_params = np.zeros(self.stride * (self.intervals + 1)) + if self.h_nav is None: + # order [a[0], b[0], a[1], b[1],...] + all_params = np.dstack((a, b)).flatten() + else: + c = np.zeros(self.intervals+1) + c[0:self.intervals//2] = b[0:self.intervals//2] + c[self.intervals//2:self.intervals+1] = a[self.intervals//2:self.intervals+1] + # order [a[0], b[0], c[0], a[1], b[1], c[1],...] + all_params = np.dstack((a, b, c)).flatten() + return all_params + + def prepare_reference_state(self): + """Prepare a circuit generating the HF reference state.""" + return get_reference_circuit(n_spinorbitals=self.n_spinorbitals, n_electrons=self.n_electrons, mapping=self.mapping, + up_then_down=self.up_then_down, spin=self.spin) + + def build_circuit(self, var_params=None): + """Build the VSQS circuit by successive first- or second-order trotterizations of h_init, h_final and possibly h_nav""" + reference_state_circuit = self.prepare_reference_state() if self.reference_state is None else self.reference_state + self.var_params = self.set_var_params(var_params) + + vsqs_circuit = get_exponentiated_qubit_operator_circuit(self.h_init, time=self.dt, trotter_order=self.trotter_order, pauli_order=self.h_init_list) + for i in range(self.intervals-1): + vsqs_circuit += get_exponentiated_qubit_operator_circuit(self.h_init, time=self.var_params[i * self.stride] * self.dt, variational=True, + trotter_order=self.trotter_order, pauli_order=self.h_init_list) + vsqs_circuit += get_exponentiated_qubit_operator_circuit(self.h_final, time=self.var_params[i * self.stride + 1] * self.dt, variational=True, + trotter_order=self.trotter_order, pauli_order=self.h_final_list) + if self.h_nav is not None: + vsqs_circuit += get_exponentiated_qubit_operator_circuit(self.h_nav, time=self.var_params[i * self.stride + 2] * self.dt, variational=True, + trotter_order=self.trotter_order, pauli_order=self.h_nav_list) + vsqs_circuit += get_exponentiated_qubit_operator_circuit(self.h_final, time=self.dt, trotter_order=self.trotter_order, pauli_order=self.h_final_list) + + self.circuit = reference_state_circuit + vsqs_circuit if reference_state_circuit.size != 0 else vsqs_circuit + + return self.circuit diff --git a/tangelo/toolboxes/molecular_computation/molecule.py b/tangelo/toolboxes/molecular_computation/molecule.py index e152145b7..0e00c1389 100644 --- a/tangelo/toolboxes/molecular_computation/molecule.py +++ b/tangelo/toolboxes/molecular_computation/molecule.py @@ -21,9 +21,10 @@ import numpy as np from pyscf import gto, scf, ao2mo import openfermion -import openfermionpyscf +import openfermion.ops.representations as reps from openfermionpyscf import run_pyscf -from openfermion.ops.representations.interaction_operator import get_active_space_integrals +from openfermion.chem.molecular_data import spinorb_from_spatial +from openfermion.ops.representations.interaction_operator import get_active_space_integrals as of_get_active_space_integrals from tangelo.toolboxes.molecular_computation.frozen_orbitals import get_frozen_core from tangelo.toolboxes.qubit_mappings.mapping_transform import get_fermion_operator @@ -275,14 +276,11 @@ def _get_fermionic_hamiltonian(self): FermionOperator: Self-explanatory. """ - occupied_indices = self.frozen_occupied - active_indices = self.active_mos + core_constant, one_body_integrals, two_body_integrals = self.get_active_space_integrals() - of_molecule = self.to_openfermion(self.basis) - of_molecule = run_pyscf(of_molecule, run_scf=True, run_mp2=False, - run_cisd=False, run_ccsd=False, run_fci=False) + one_body_coefficients, two_body_coefficients = spinorb_from_spatial(one_body_integrals, two_body_integrals) - molecular_hamiltonian = of_molecule.get_molecular_hamiltonian(occupied_indices, active_indices) + molecular_hamiltonian = reps.InteractionOperator(core_constant, one_body_coefficients, 1 / 2 * two_body_coefficients) return get_fermion_operator(molecular_hamiltonian) @@ -379,6 +377,28 @@ def energy_from_rdms(self, one_rdm, two_rdm): float: Molecular energy. """ + core_constant, one_electron_integrals, two_electron_integrals = self.get_active_space_integrals() + + # PQRS convention in openfermion: + # h[p,q]=\int \phi_p(x)* (T + V_{ext}) \phi_q(x) dx + # h[p,q,r,s]=\int \phi_p(x)* \phi_q(y)* V_{elec-elec} \phi_r(y) \phi_s(x) dxdy + # The convention is not the same with PySCF integrals. So, a change is + # reverse back after performing the truncation for frozen orbitals + two_electron_integrals = two_electron_integrals.transpose(0, 3, 1, 2) + + # Computing the total energy from integrals and provided RDMs. + e = core_constant + np.sum(one_electron_integrals * one_rdm) + 0.5*np.sum(two_electron_integrals * two_rdm) + + return e.real + + def get_active_space_integrals(self): + """Computes core constant, one_body, and two-body coefficients with frozen orbitals folded into one-body coefficients + and core constant + + Returns: + (float, array, array): (core_constant, one_body coefficients, two_body coefficients) + """ + # Pyscf molecule to get integrals. pyscf_mol = self.to_pyscf(self.basis) @@ -396,19 +416,14 @@ def energy_from_rdms(self, one_rdm, two_rdm): # h[p,q]=\int \phi_p(x)* (T + V_{ext}) \phi_q(x) dx # h[p,q,r,s]=\int \phi_p(x)* \phi_q(y)* V_{elec-elec} \phi_r(y) \phi_s(x) dxdy # The convention is not the same with PySCF integrals. So, a change is - # made and reverse back after performing the truncation for frozen - # orbitals. + # made before performing the truncation for frozen orbitals. two_electron_integrals = two_electron_integrals.transpose(0, 2, 3, 1) - core_offset, one_electron_integrals, two_electron_integrals = get_active_space_integrals(one_electron_integrals, - two_electron_integrals, - self.frozen_occupied, - self.active_mos) - two_electron_integrals = two_electron_integrals.transpose(0, 3, 1, 2) + core_offset, one_electron_integrals, two_electron_integrals = of_get_active_space_integrals(one_electron_integrals, + two_electron_integrals, + self.frozen_occupied, + self.active_mos) # Adding frozen electron contribution to core constant. core_constant += core_offset - # Computing the total energy from integrals and provided RDMs. - e = core_constant + np.sum(one_electron_integrals * one_rdm) + 0.5*np.sum(two_electron_integrals * two_rdm) - - return e.real + return core_constant, one_electron_integrals, two_electron_integrals From c9fddb87d79523a2434ca1e11fb891418b56a5ad Mon Sep 17 00:00:00 2001 From: elloyd-1qbit <58313607+elloyd-1qbit@users.noreply.github.com> Date: Wed, 19 Jan 2022 09:54:16 -0800 Subject: [PATCH 56/68] Fix for #107 circuit stack (#115) * circuit stacking bug fix using deep copies Co-authored-by: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> --- tangelo/algorithms/variational/adapt_vqe_solver.py | 6 ++---- tangelo/linq/circuit.py | 5 +++-- tangelo/linq/tests/test_circuits.py | 8 ++++++++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/tangelo/algorithms/variational/adapt_vqe_solver.py b/tangelo/algorithms/variational/adapt_vqe_solver.py index 8a32a01f9..b7272b427 100644 --- a/tangelo/algorithms/variational/adapt_vqe_solver.py +++ b/tangelo/algorithms/variational/adapt_vqe_solver.py @@ -250,10 +250,8 @@ def rank_pool(self, pool_commutators, circuit, backend, tolerance=1e-3): Args: pool_commutators (QubitOperator): Commutator [H, operator] for each generator. - circuit (agnostic_simulator.Circuit): Circuit for measuring each - commutator. - backend (tangelo.linq.Simulator): Backend to measure - expectation values. + circuit (tangelo.linq.Circuit): Circuit for measuring each commutator. + backend (tangelo.linq.Simulator): Backend to compute expectation values. tolerance (float): Minimum value for gradient to be considered. Returns: diff --git a/tangelo/linq/circuit.py b/tangelo/linq/circuit.py index 3220874bf..895acb901 100644 --- a/tangelo/linq/circuit.py +++ b/tangelo/linq/circuit.py @@ -281,7 +281,8 @@ def stack(*circuits): # Stack circuits. Reindex each circuit with the proper offset and then concatenate, until done stacked_circuit = circuits.pop(0) for c in circuits: - c.reindex_qubits(list(range(stacked_circuit.width, stacked_circuit.width + c.width))) - stacked_circuit += c + c_stack = copy.deepcopy(c) + c_stack.reindex_qubits(list(range(stacked_circuit.width, stacked_circuit.width + c.width))) + stacked_circuit += c_stack return stacked_circuit diff --git a/tangelo/linq/tests/test_circuits.py b/tangelo/linq/tests/test_circuits.py index d177d8abc..708e25795 100644 --- a/tangelo/linq/tests/test_circuits.py +++ b/tangelo/linq/tests/test_circuits.py @@ -183,6 +183,14 @@ def test_stack_circuits(self): # Try convenience method in Circuit class self.assertTrue(ref == c1.stack(c2, c3)._gates) + c4 = Circuit([Gate("H", 0), Gate("CNOT", 1, control=0), Gate("X", 0), Gate("RX", 1, parameter=2.)]) + + ref2 = [Gate("H", 0), Gate("CNOT", 1, control=0), Gate("X", 0), Gate("RX", 1, parameter=2.), + Gate("H", 2), Gate("CNOT", 3, control=2), Gate("X", 2), Gate("RX", 3, parameter=2.)] + # Stacked copies of same circuit + self.assertTrue(ref2 == stack(c4, c4)._gates) + self.assertTrue(ref2 == c4.stack(c4)._gates) + def test_equality_circuit(self): """ Test equality operators (== and !=) for circuits """ self.assertTrue(circuit1 == circuit2) From 15e4a98adb8fbbb1066a3835f699b14e8bf221ea Mon Sep 17 00:00:00 2001 From: KrzysztofB-1qbit <86750444+KrzysztofB-1qbit@users.noreply.github.com> Date: Wed, 19 Jan 2022 16:25:09 -0800 Subject: [PATCH 57/68] Staged richardson (#99) * Added extrapolation functions and unit tests --- tangelo/toolboxes/post_processing/__init__.py | 1 + .../post_processing/extrapolation.py | 158 ++++++++++++++++++ .../tests/test_extrapolation.py | 42 +++++ 3 files changed, 201 insertions(+) create mode 100644 tangelo/toolboxes/post_processing/extrapolation.py create mode 100644 tangelo/toolboxes/post_processing/tests/test_extrapolation.py diff --git a/tangelo/toolboxes/post_processing/__init__.py b/tangelo/toolboxes/post_processing/__init__.py index b73851e5e..dab23877f 100644 --- a/tangelo/toolboxes/post_processing/__init__.py +++ b/tangelo/toolboxes/post_processing/__init__.py @@ -13,3 +13,4 @@ # limitations under the License. from .mc_weeny_rdm_purification import mcweeny_purify_2rdm +from .extrapolation import diis, richardson diff --git a/tangelo/toolboxes/post_processing/extrapolation.py b/tangelo/toolboxes/post_processing/extrapolation.py new file mode 100644 index 000000000..f9bf8f048 --- /dev/null +++ b/tangelo/toolboxes/post_processing/extrapolation.py @@ -0,0 +1,158 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np +import scipy.optimize as sp + + +def diis(energies, coeffs): + """ + DIIS extrapolation, originally developped by Pulay in + Chemical Physics Letters 73, 393-398 (1980) + + Args: + energies (array-like): Energy expectation values for amplified noise rates + coeffs (array-like): Noise rate amplification factors + + Returns: + float: Extrapolated energy + """ + return extrapolation(energies, coeffs, 1) + + +def richardson(energies, coeffs, estimate_exp=False): + """ + General, DIIS-like extrapolation procedure as found in + Nature 567, 491-495 (2019) [arXiv:1805.04492] + + Args: + energies (array-like): Energy expectation values for amplified noise rates + coeffs (array-like): Noise rate amplification factors + + Returns: + float: Extrapolated energy + """ + if estimate_exp is False: + # If no exponent estimation, run the direct Richardson solution + return richardson_analytical(energies, coeffs) + else: + # For exponent estimation run the Richardson recursive algorithm + return richardson_with_exp_estimation(energies, coeffs) + + +def extrapolation(energies, coeffs, taylor_order=None): + """ + General, DIIS-like extrapolation procedure as found in + Nature 567, 491-495 (2019) [arXiv:1805.04492] + + Args: + energies (array-like): Energy expectation values for amplified noise rates + coeffs (array-like): Noise rate amplification factors + taylor_order (int): Taylor expansion order; None for Richardson extrapolation (order determined from number of datapoints), 1 for DIIS extrapolation + + Returns: + float: Extrapolated energy + """ + n = len(coeffs) + if taylor_order is None: + # Determine the expansion order in case of Richardson extrapolation + taylor_order = n-1 + Eh = np.array(energies) + coeffs = np.array(coeffs) + + # Setup the linear system matrix + ck = np.array([coeffs**k for k in range(1, taylor_order+1)]) + B = np.ones((n+1, n+1)) + B[n, n] = 0 + B[:n, :n] = ck.T @ ck + + # Setup the free coefficients + b = np.zeros(n+1) + b[n] = 1 # For the Lagrange multiplier + + # Solve the DIIS equations by least squares + x = np.linalg.lstsq(B, b, rcond=None)[0] + return np.dot(Eh, x[:-1]) + + +def richardson_analytical(energies, coeffs): + """ + Richardson extrapolation explicit result as found in + Phys. Rev. Lett. 119, 180509 [arXiv:1612.02058] + + Args: + energies (array-like): Energy expectation values for amplified noise rates + coeffs (array-like): Noise rate amplification factors + + Returns: + float: Extrapolated energy + """ + Eh = np.array(energies) + ck = np.array(coeffs) + x = np.array([np.prod(ai/(a - ai)) for i, a in enumerate(ck) + for ai in [np.delete(ck, i)]]) + return np.dot(Eh, x) + + +def richardson_with_exp_estimation(energies, coeffs): + """ + Richardson extrapolation by recurrence, with exponent estimation + + Args: + energies (array-like): Energy expectation values for amplified noise rates + coeffs (array-like): Noise rate amplification factors + + Returns: + float: Extrapolated energy + """ + n = len(coeffs) + Eh = np.array(energies) + c = np.array(coeffs) + ck = np.array(coeffs) + p, p_old = 1, 0 + + # Define a helper function for exponent optimization + def energy_diff(k, ti, si): + tk = np.sign(ti)*np.abs(ti)**k + sk = np.sign(si)*np.abs(si)**k + Et = (tk*Eh[1] - Eh[0])/(tk - 1) + Es = (sk*Eh[2] - Eh[0])/(sk - 1) + return (Et - Es)**2 + + # Run the Richardson algorithm with exponent optimization + for i in range(n-1): + ti = ck[0]/ck[1] + si = ck[0]/ck[2] + if ((n > 2) and (i < (n-2))): + # Minimize the energy difference to determine the optimal exponent + p = sp.minimize(energy_diff, p+1, args=(ti, si), method='BFGS', options={'disp': False}).x[0] + if (i == 0): + ck = c**p + else: + break + + # Run the main Richardson loop + for j in range(n-i-1): + ti = (ck[j]/ck[j+1]) + if (i > 0): + dp = p - p_old + if (np.isclose(dp, 0)): + break + ck[j] = ck[j]*(c[j]**dp - c[j+1]**dp)/(ti - 1) + ti = (ck[j]/ck[j+1]) + else: + ck[j] = ck[j]*(c[j] - c[j+1])/(ti - 1) + Eh[j] = (ti*Eh[j+1] - Eh[j])/(ti - 1) + p_old = p + return(Eh[0]) diff --git a/tangelo/toolboxes/post_processing/tests/test_extrapolation.py b/tangelo/toolboxes/post_processing/tests/test_extrapolation.py new file mode 100644 index 000000000..6f8ffd88f --- /dev/null +++ b/tangelo/toolboxes/post_processing/tests/test_extrapolation.py @@ -0,0 +1,42 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest +import numpy as np + +from tangelo.toolboxes.post_processing import diis, richardson + +energies = [-1.04775574, -1.04302289, -1.03364568, -1.03005245] +coeffs = [1., 1.1, 1.2, 1.3] + + +class ExtrapolationTest(unittest.TestCase): + + def test_diis(self): + """Test DIIS extrapolation on small sample data + """ + calculated = diis(energies, coeffs) + self.assertAlmostEqual(-1.11047933, calculated, delta=1e-6) + + def test_richardson(self): + """Test Richardson extrapolation on small sample data + """ + calculated = richardson(energies, coeffs) + self.assertAlmostEqual(-1.45459036, calculated, delta=1e-6) + calculated = richardson(energies, coeffs, estimate_exp=True) + self.assertAlmostEqual(-1.05601603, calculated, delta=1e-6) + + +if __name__ == "__main__": + unittest.main() From 6d33204c012011ed6ff4a70e83b0426f11538aaa Mon Sep 17 00:00:00 2001 From: JamesB-1qbit <84878946+JamesB-1qbit@users.noreply.github.com> Date: Wed, 19 Jan 2022 19:37:26 -0500 Subject: [PATCH 58/68] Majorana pool (#114) * added Majorana uccds and uccgsd pools for ADAPT-VQE --- examples/adapt.ipynb | 232 +++++++++--------- .../variational/adapt_vqe_solver.py | 8 +- .../tests/test_adapt_vqe_solver.py | 27 +- .../ansatz_generator/_unitary_majorana_cc.py | 101 ++++++++ 4 files changed, 247 insertions(+), 121 deletions(-) create mode 100644 tangelo/toolboxes/ansatz_generator/_unitary_majorana_cc.py diff --git a/examples/adapt.ipynb b/examples/adapt.ipynb index 8dce2522d..6fd927f79 100755 --- a/examples/adapt.ipynb +++ b/examples/adapt.ipynb @@ -1,31 +1,8 @@ { - "metadata": { - "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.8.10" - }, - "orig_nbformat": 2, - "kernelspec": { - "name": "python3", - "display_name": "Python 3.8.10 64-bit ('agnostic': venv)" - }, - "interpreter": { - "hash": "fd77f6ebaf3d18999f00320d0aca64091b39e7847b653c69719c9ddc4e72c63f" - } - }, - "nbformat": 4, - "nbformat_minor": 2, "cells": [ { "cell_type": "markdown", + "metadata": {}, "source": [ "# ADAPT-VQE\n", "\n", @@ -35,11 +12,11 @@ "In ADAPT-VQE, an ansatz which approximates not UCCSD/UCCGSD, but in fact FCI, is built iteratively. Over a series of cycles, the ansatz circuit is grown to achieve an approximation to FCI with a minimal number of circuit elements. In this way, ADAPT-VQE can be thought as a meta-VQE: at each cycle, a new ansatz is defined, and its parameters optimized according to conventional VQE. As the cycles proceed, the ansatz grows in both complexity and expressibility. This algorithm comes at the expense of a significant increase in measurement overhead. In order to identify the best operator to append to the present ansatz circuit, a large number of measurements are performed to rank the available operators in order of their ability to further reduce the ansatz state energy.\n", "\n", "In this notebook, we explore the implementation of this algorithm, available in Tangelo. The original algorithm is examined first, and has shown some success in reducing the number of variational parameters required to express the quantum state. Then, we examine another version of ADAPT-VQE which is successful at reducing the circuit size by using a pool of operators defined from the Qubit Hamiltonian." - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## Original ADAPT-VQE\n", "\n", @@ -51,21 +28,24 @@ "4. Return to step 2.\n", "\n", "The UCCGSD pool allows for high accuracy with a small number of cycles and consequently very few variational parameters. However, the circuit remains very deep, as each excitation consists of many multi-qubit Pauli rotations. " - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### Running the Adapt-VQE algorithm\n", "\n", "We define a molecule $H_4$ in this case and use the `AdaptSolver` class to run Adapt-VQE." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "from tangelo import SecondQuantizedMolecule\n", "from tangelo.algorithms import ADAPTSolver\n", @@ -78,23 +58,21 @@ "adapt_solver = ADAPTSolver(opt_dict)\n", "adapt_solver.build()\n", "adapt_solver.simulate()" - ], - "outputs": [], - "metadata": { - "tags": [] - } + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "After 7 cycles, we force the algorithm to terminate. \n", "We can now compare the results against the predictions of FCI." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import numpy as np\n", @@ -110,86 +88,86 @@ "ax.set_title('ADAPT-VQE: H$_4$')\n", "ax.set_yscale('log')\n", "print(f'Final Error: {errors[-1] :.4E}')" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Ok, so after 7 cycles, we have an error of 0.9 mHa, within chemical accuracy of FCI. How does this all compare against UCCSD-VQE?" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "from tangelo.algorithms import VQESolver, BuiltInAnsatze\n", "\n", "vqe_solver = VQESolver({'molecule': mol, 'ansatz': BuiltInAnsatze.UCCSD})\n", "vqe_solver.build()\n", "vqe_solver.simulate()" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "print(f'ADAPT-VQE ERROR: {adapt_solver.energies[-1] - exact :0.4E} Ha')\n", "print(f'UCCSD-VQE ERROR: {vqe_solver.optimal_energy - exact :0.4E} Ha')" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "From the perspective of energy accuracy, the two have reached very similar results. The big advantage here however, are in the resources required for the ADAPT-VQE ansatz circuit:" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "print(f'ADAPT RESOURCES:\\n {adapt_solver.get_resources()}\\n')\n", "print(f'UCCSD RESOURCES:\\n {vqe_solver.get_resources()}')" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "We have managed here to reduce the total number of variational parameters by a factor of two while keeping all the gate requirements similar. With ADAPT-VQE, the scaling of the number of parameters that need to be optimized should be better than the $\\mathcal{O}(N^2M^2)$ scaling of UCCSD-VQE. \n", "\n", "There is another version of ADAPT-VQE which focuses on reducing the circuit depth instead of reducing the number of variational parameters. Although, this version can also reduce the number of variational parameters by truncating the number of cycles, it is not as efficient in the respect as the orignal ADAPT-VQE implementation. This is what we will explore next." - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## Reducing circuit depth with ADAPT-VQE\n", "\n", "There are two obvious avenues along which one can be creative in the implementation of ADAPT-VQE--these are the selection of the operator pool $\\{P_i\\}$, and the ranking strategy. Above, we have specified that we are using the gradient to perform this ranking procedure. This is the only method which has been used previously, and we will continue to do so here. Regarding the choice of operator pool, the original method used the UCCGSD ansatz to generate the pool, i.e. the single, double fermionic excitations. Subsequent work has focused on qubit-native operators, and we'll follow this strategy as well. As we have shown above, the UCCGSD pool allows for high accuracy with a small number of cycles (and consequently very few variational parameters), but the circuit remains very deep. We will now show how to use a custom Hamiltonian-inspired qubit-pool to generate shorter circuits." - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### Hamiltonian-Inspired Qubit-ADAPT\n", "We are going to use the qubit Hamiltonian $H$ to establish a set of pool generators to use in our implementation of ADAPT-VQE. For each term in the Hamiltonian which acts on a distinct combination of qubits, we'll add a pool operator which is guaranteed to have a non-zero commutator with respect to H, and therefore a finite gradient. This will give us a fairly compact set of operators in the pool, all of which are likely to be useful in our ansatz construction. To do this, we will scan through all terms in the qubit Hamiltonian. For each, we will identify the Pauli operators associated with electronic excitation (X and Y gates). We discard Z gates from the Pauli string. If the number of Y gates in a string is even, we can use this to construct an excitation pool element that preserves T-symmetry, by flipping one of the other X gates to a Y, or a Y to an X. Below, we'll flip the first X or Y to its partner." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "def get_pool(qubit_hamiltonian, n_qubits):\n", " \"\"\"Use Hamiltonian to identify non-commuting Pauli strings to use as operator pool.\n", @@ -240,20 +218,20 @@ " pool_tuples.append(operator_tuple)\n", " \n", " return pool_generators" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "To exemplify the generation of an operator pool, we will apply this method to a simple Hamiltonian consisting of a single fermionic excitation and its Hermitian conjugate." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "from tangelo.toolboxes.operators import QubitOperator\n", "qubit_operator = QubitOperator(((0, 'X'), (1, 'X'), (2, 'Y'), (3, 'Y')), -1.0) \\\n", @@ -262,28 +240,28 @@ " + QubitOperator(((0, 'Y'), (1, 'Y'), (2, 'X'), (3, 'X')), -1.0) \n", "pool_generators = get_pool(qubit_operator, n_qubits=4)\n", "print(f'OPERATOR POOL: {pool_generators}')" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "We have used a qubit Hamiltonian where all operators act on the same qubit indices. This results in just a single generator pool element. As we traverse the `qubit_hamiltonian` in order, the generator corresponds to the first term in the Hamiltonian, with the first X operator switched to a Y operator. So from XXYY, we have YXYY. " - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "### Defining the problem\n", "For the generator pool we've considered here, the smallest non-trivial problem to consider is $H_4$, as $H_2$ has only one generator and ADAPT terminates at FCI accuracy after a single iteration. So we'll apply the qubit-ADAPT algorithm to the same $H_4$ molecule as we did above with the UCCGSD pool. We obtain the qubit operator using `fermion_to_qubit_mapping` to make use of the pool function defined above." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping\n", "from tangelo.toolboxes.operators import count_qubits\n", @@ -291,27 +269,27 @@ "fermion_operator = mol._get_fermionic_hamiltonian()\n", "qubit_operator = fermion_to_qubit_mapping(fermion_operator, 'jw', mol.n_active_sos, mol.n_electrons)\n", "n_qubits = count_qubits(qubit_operator)" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "With the problem defined and the `get_pool` function prepared, we can proceed to initiate the ADAPT-VQE method. This is as simple as defining the problem with a options dictionary and initiating the `ADAPTSolver` class." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "from tangelo.algorithms import ADAPTSolver\n", "\n", "opt_dict = {\"molecule\": mol,\n", " \"frozen_orbitals\": 0,\n", " \"pool\": get_pool,\n", - " \"pool_args\": (qubit_operator, n_qubits),\n", + " \"pool_args\": {\"qubit_hamiltonian\": qubit_operator, \"n_qubits\": n_qubits},\n", " \"tol\": 0.01,\n", " \"max_cycles\": 12,\n", " \"verbose\": False}\n", @@ -319,20 +297,20 @@ "adapt_solver = ADAPTSolver(opt_dict)\n", "adapt_solver.build()\n", "adapt_solver.simulate()" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "After 12 cycles, we force the algorithm to terminate. We can now compare the results against the predictions of FCI." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "errors = np.array(adapt_solver.energies) - exact\n", "fig,ax = plt.subplots(1,1)\n", @@ -342,46 +320,45 @@ "ax.set_title('qubit-ADAPT: H$_4$')\n", "ax.set_yscale('log')\n", "print(f'Final Error: {errors[-1] :.4E}')" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Ok so after 12 cycles, we have an error of 1.4 mHa, within chemical accuracy of FCI. How does this all compare against UCCSD-VQE?" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "print(f'ADAPT-VQE ERROR: {adapt_solver.energies[-1] - exact :0.4E} Ha')\n", "print(f'UCCSD-VQE ERROR: {vqe_solver.optimal_energy - exact :0.4E} Ha')" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "From the perspective of energy accuracy, the two have reached very similar results, within a factor of two. The big advantage here however, are in the resources required for this ansatz circuit:" - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "print(f'ADAPT RESOURCES:\\n {adapt_solver.get_resources()}\\n')\n", "print(f'UCCSD RESOURCES:\\n {vqe_solver.get_resources()}')" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "\n", "We have managed here to reduce the total number of gates, the number of 2-qubit gates, and the number of variational gates by an order of magnitude. \n", @@ -391,12 +368,13 @@ "### Restricting Quantum numbers\n", "\n", "There is one small problem with a qubit inspired ansatz. It does not generally respect the symmetry of the problem. We can now examine the $\\hat{S}^2$, $\\hat{S}_Z$ and the $\\hat{N}$ operators to determine if any loss of symmetry has occured. For this problem, the ground state is a singlet with four electrons so the expected values are $S^2=0$, $S_Z=0$ and $N=4$ respectively." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "exact_s2 = 0\n", "exact_sz = 0\n", @@ -410,20 +388,20 @@ "\n", "print(f'Adapt quantum numbers errors: N = {adapt_n-exact_n:6.4f}, Sz = {adapt_sz-exact_sz:6.4f}, S^2 = {adapt_s2-exact_s2:6.4f}')\n", "print(f'UCCSD quantum numbers errors: N = {uccsd_n-exact_n:6.4f}, Sz = {uccsd_sz-exact_sz:6.4f}, S^2 = {uccsd_s2-exact_s2:6.4f}')" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "As you can see, there are small errors in the desired quantum numbers with qubit-Adapt while UCCSD has the correct expectation values. Therefore, the resulting representation is not exactly what we wanted. To address this problem we can add a penalty term to the qubit hamiltonian and minimize this modified `qubit_hamiltonian` directly using Adapt-VQE instead of the molecular hamiltonian. Generating this new Hamiltonian is as simple as creating a dictionary of `[penalty_weight, desired_quantum_number]` for each operator and using the `combined_penalty` function. For this example, we choose a `penalty_weight` of 1/2 for each term which is usually a reasonable choice." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "from tangelo.toolboxes.ansatz_generator.penalty_terms import combined_penalty\n", "from tangelo.toolboxes.operators.operators import qubitop_to_qubitham\n", @@ -446,7 +424,7 @@ " \"n_electrons\": mol.n_electrons,\n", " \"n_spinorbitals\": mol.n_active_sos, \n", " \"pool\": get_pool,\n", - " \"pool_args\": (qubit_hamiltonian_with_pen, n_qubits),\n", + " \"pool_args\": {\"qubit_hamiltonian\": qubit_hamiltonian_with_pen, \"n_qubits\": n_qubits},\n", " \"tol\": 0.01,\n", " \"max_cycles\": 12,\n", " \"verbose\": False}\n", @@ -454,20 +432,20 @@ "adapt_solver_with_pen = ADAPTSolver(opt_dict)\n", "adapt_solver_with_pen.build()\n", "adapt_solver_with_pen.simulate()" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "We can now check that minimizing this new Hamiltonian resulted in a state with the correct operator expectation values." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "# Since we initialized VQESolver with a QubitHamiltonian, we need to provide the corresponding number of molecular\n", "# orbitals to use the built-in operators S^2, Sz and N.\n", @@ -481,25 +459,47 @@ "print(f'UCCSD RESOURCES:\\n {vqe_solver.get_resources()}\\n')\n", "print(f'Adapt quantum numbers: N = {adapt_with_pen_n - exact_n:6.4f}, Sz = {adapt_with_pen_sz - exact_sz:6.4f}, S^2 = {adapt_with_pen_s2 - exact_s2:6.4f}')\n", "print(f'UCCSD quantum numbers: N = {uccsd_n - exact_n:6.4f}, Sz = {uccsd_sz - exact_sz:6.4f}, S^2 = {uccsd_s2 - exact_s2:6.4f}')" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "As you can see, we have now found a state that has the correct symmetry properties but the resulting energy is now larger than chemical accuracy. Therefore, more cycles of ADAPT-VQE are needed if chemical accuracy is desired." - ], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "## Conclusion\n", "\n", "In this notebook, we've explored an implementation of the original ADAPT-VQE algorithm, and the Hamiltonian-inspired qubit variant, using the tools available in Tangelo. It is clear that the number of parameters required for accurate results can be made much smaller with the orignal algorithm, while the qubit version can reduce the circuit depth greatly. The second section illustrates how users can create their own pool of qubit operators through their own `get_pool` function, to explore their own avenues. " - ], - "metadata": {} + ] } - ] + ], + "metadata": { + "interpreter": { + "hash": "fd77f6ebaf3d18999f00320d0aca64091b39e7847b653c69719c9ddc4e72c63f" + }, + "kernelspec": { + "display_name": "Python 3.8.10 64-bit ('agnostic': venv)", + "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.8.10" + }, + "orig_nbformat": 2 + }, + "nbformat": 4, + "nbformat_minor": 2 } diff --git a/tangelo/algorithms/variational/adapt_vqe_solver.py b/tangelo/algorithms/variational/adapt_vqe_solver.py index b7272b427..7db4878e1 100644 --- a/tangelo/algorithms/variational/adapt_vqe_solver.py +++ b/tangelo/algorithms/variational/adapt_vqe_solver.py @@ -53,8 +53,8 @@ class ADAPTSolver: pool (func): Function that returns a list of FermionOperator. Each element represents excitation/operator that has an effect of the total energy. - pool_args (tuple) : The arguments for the pool function given as a - tuple. + pool_args (dict) : The arguments for the pool function. Will be unpacked in + function call as pool(**pool_args) qubit_mapping (str): One of the supported qubit mapping identifiers. qubit_hamiltonian (QubitOperator-like): Self-explanatory. up_then_down (bool): Spin orbitals ordering. @@ -170,11 +170,11 @@ def build(self): # are added, this part must be modified and generalized. if self.pool_args is None: if self.pool == uccgsd_pool: - self.pool_args = (self.n_spinorbitals,) + self.pool_args = {"n_qubits": self.n_spinorbitals} else: raise KeyError('pool_args must be defined if using own pool function') # Check if pool function returns a QubitOperator or FermionOperator and populate variables - pool_list = self.pool(*self.pool_args) + pool_list = self.pool(**self.pool_args) if isinstance(pool_list[0], QubitOperator): self.pool_type = 'qubit' self.pool_operators = pool_list diff --git a/tangelo/algorithms/variational/tests/test_adapt_vqe_solver.py b/tangelo/algorithms/variational/tests/test_adapt_vqe_solver.py index 146b76e69..53d09d502 100644 --- a/tangelo/algorithms/variational/tests/test_adapt_vqe_solver.py +++ b/tangelo/algorithms/variational/tests/test_adapt_vqe_solver.py @@ -15,7 +15,9 @@ import unittest from tangelo.algorithms.variational import ADAPTSolver -from tangelo.molecule_library import mol_H2_sto3g +from tangelo.molecule_library import mol_H2_sto3g, xyz_H4 +from tangelo.toolboxes.ansatz_generator._unitary_majorana_cc import get_majorana_uccgsd_pool, get_majorana_uccsd_pool +from tangelo.toolboxes.molecular_computation.molecule import SecondQuantizedMolecule class ADAPTSolverTest(unittest.TestCase): @@ -54,6 +56,29 @@ def test_single_cycle_adapt(self): "vqe_variational_parameters": 1} self.assertEqual(adapt_solver.get_resources(), resources) + def test_multiple_cycle_adapt_majorana_pool(self): + """Solve H4 with one frozen orbtial with ADAPTSolver using 4 cycles and operators chosen + from a Majorana UCCGSD pool and a Majorana UCCSD pool + """ + + mol = SecondQuantizedMolecule(xyz_H4, 0, 0, "sto-3g", frozen_orbitals=[0]) + opt_dict = {"molecule": mol, "max_cycles": 4, "verbose": False, "pool": get_majorana_uccgsd_pool, + "pool_args": {"n_sos": mol.n_active_sos}} + adapt_solver = ADAPTSolver(opt_dict) + adapt_solver.build() + adapt_solver.simulate() + + self.assertAlmostEqual(adapt_solver.optimal_energy, -1.8945, places=3) + + mol = SecondQuantizedMolecule(xyz_H4, 0, 0, "sto-3g", frozen_orbitals=[0]) + opt_dict = {"molecule": mol, "max_cycles": 4, "verbose": False, "pool": get_majorana_uccsd_pool, + "pool_args": {"n_electrons": mol.n_active_electrons, "n_sos": mol.n_active_sos}} + adapt_solver = ADAPTSolver(opt_dict) + adapt_solver.build() + adapt_solver.simulate() + + self.assertAlmostEqual(adapt_solver.optimal_energy, -1.8945, places=3) + if __name__ == "__main__": unittest.main() diff --git a/tangelo/toolboxes/ansatz_generator/_unitary_majorana_cc.py b/tangelo/toolboxes/ansatz_generator/_unitary_majorana_cc.py new file mode 100644 index 000000000..6aa2a9110 --- /dev/null +++ b/tangelo/toolboxes/ansatz_generator/_unitary_majorana_cc.py @@ -0,0 +1,101 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Utililty functions to generate pool of FermionOperators obtained from individual MajoranaOperators in +unitary coupled cluster expansions.""" + +from openfermion.transforms.opconversions.conversions import get_fermion_operator +from openfermion import MajoranaOperator +from numpy import integer + +from tangelo.toolboxes.molecular_computation.molecule import SecondQuantizedMolecule + + +def get_majorana_uccsd_pool(molecule: SecondQuantizedMolecule = None, n_electrons: int = None, n_sos: int = None): + """Construct a list of FermionOperator corresponding to the individual Majorana modes in the UCCSD ansatz + + Args: + molecule (SecondQuantizedMolecule): The molecule to generate the Majorana pool from: Default None + n_electrons (int): The number of active electrons: Default None + n_sos (int): The number of active spin orbitals: Default None + + Returns: + list: The list of FermionOperator for each Majorana operator in the UCCSD pool + """ + + if molecule is not None: + n_active_electrons = molecule.n_active_electrons + n_active_sos = molecule.n_active_sos + elif isinstance(n_electrons, (int, integer)) and isinstance(n_sos, (int, integer)): + n_active_electrons = n_electrons + n_active_sos = n_sos + else: + raise ValueError("SecondQuantized mol or ints n_electrons/n_sos must be provided") + + def majorana_uccsd_generator(): + for i in range(n_active_electrons): + for k in range(n_active_electrons, n_active_sos): + yield (2*i, 2*k) + yield (2*i+1, 2*k+1) + for j in range(i+1, n_active_electrons): + for l in range(k+1, n_active_sos): + yield (2*i, 2*j+1, 2*k+1, 2*l+1) + yield (2*i+1, 2*j, 2*k+1, 2*l+1) + yield (2*i+1, 2*j+1, 2*k, 2*l+1) + yield (2*i+1, 2*j+1, 2*k+1, 2*l) + yield (2*i+1, 2*j, 2*k, 2*l) + yield (2*i, 2*j+1, 2*k, 2*l) + yield (2*i, 2*j, 2*k+1, 2*l) + yield (2*i, 2*j, 2*k, 2*l+1) + + pool_list = [get_fermion_operator(MajoranaOperator(term)) for term in majorana_uccsd_generator()] + return pool_list + + +def get_majorana_uccgsd_pool(molecule: SecondQuantizedMolecule = None, n_sos: int = None): + """Construct a list of FermionOperator corresponding to the individual Majorana modes in the UCCGSD ansatz + + Args: + molecule (SecondQuantizedMolecule): The molecule to generate the Majorana pool from: Default None + n_sos (int): The number of active spin orbitals: Default None + + Returns: + list: The list of FermionOperator for each Majorana operator in the UCCGSD pool + """ + + if molecule is not None: + n_active_sos = molecule.n_active_sos + elif isinstance(n_sos, (int, integer)): + n_active_sos = n_sos + else: + raise ValueError("SecondQuantized mol or int n_sos must be provided") + + def majorana_uccgsd_generator(): + for i in range(n_active_sos): + for j in range(i+1, n_active_sos): + yield (2*i, 2*j) + yield (2*i+1, 2*j+1) + for k in range(j+1, n_active_sos): + for l in range(k+1, n_active_sos): + yield (2*i, 2*j+1, 2*k+1, 2*l+1) + yield (2*i+1, 2*j, 2*k+1, 2*l+1) + yield (2*i+1, 2*j+1, 2*k, 2*l+1) + yield (2*i+1, 2*j+1, 2*k+1, 2*l) + yield (2*i+1, 2*j, 2*k, 2*l) + yield (2*i, 2*j+1, 2*k, 2*l) + yield (2*i, 2*j, 2*k+1, 2*l) + yield (2*i, 2*j, 2*k, 2*l+1) + + pool_list = [get_fermion_operator(MajoranaOperator(term)) for term in majorana_uccgsd_generator()] + return pool_list From 262da4bdd7304d64b061dc264ec549ec56e54b79 Mon Sep 17 00:00:00 2001 From: JamesB-1qbit <84878946+JamesB-1qbit@users.noreply.github.com> Date: Fri, 21 Jan 2022 13:42:30 -0500 Subject: [PATCH 59/68] removed custom functions to write/read openfermion qubit operators (#117) --- tangelo/linq/helpers/operators/operators.py | 28 ------------------- .../measurements/tests/test_measurements.py | 2 +- .../tests/test_qubit_terms_grouping.py | 2 +- 3 files changed, 2 insertions(+), 30 deletions(-) diff --git a/tangelo/linq/helpers/operators/operators.py b/tangelo/linq/helpers/operators/operators.py index a4815109c..23595658c 100644 --- a/tangelo/linq/helpers/operators/operators.py +++ b/tangelo/linq/helpers/operators/operators.py @@ -19,34 +19,6 @@ import math import numpy as np -from openfermion.ops import QubitOperator - - -def ham_of_to_string(of_qb_ham): - """Converts an Openfermion QubitOperator into a string with information for - a Pauli word per line. - """ - res = "" - for k, v in of_qb_ham.terms.items(): - res += f'{str(v)}\t{str(k)}\n' - return res - - -def string_ham_to_of(string_ham): - """Reverse function of ham_of_to_string : reads a Hamiltonian from a file - that uses the Openfermion syntax, loads it into an openfermion - QubitOperator. - """ - of_terms_dict = dict() - string_ham = string_ham.split('\n')[:-1] - - for term in string_ham: - coef, word = term.split('\t') - of_terms_dict[eval(word)] = eval(coef) - - res = QubitOperator() - res.terms = of_terms_dict - return res def print_histogram_coeffs(qb_ham): diff --git a/tangelo/toolboxes/measurements/tests/test_measurements.py b/tangelo/toolboxes/measurements/tests/test_measurements.py index 3a23d1a3f..ed2eec284 100644 --- a/tangelo/toolboxes/measurements/tests/test_measurements.py +++ b/tangelo/toolboxes/measurements/tests/test_measurements.py @@ -18,7 +18,7 @@ from tangelo.helpers.utils import default_simulator from tangelo.linq import translator, Simulator, Circuit -from tangelo.linq.helpers import string_ham_to_of, measurement_basis_gates +from tangelo.linq.helpers import measurement_basis_gates from tangelo.toolboxes.operators import QubitOperator from tangelo.toolboxes.measurements import get_measurement_estimate diff --git a/tangelo/toolboxes/measurements/tests/test_qubit_terms_grouping.py b/tangelo/toolboxes/measurements/tests/test_qubit_terms_grouping.py index 4721d4115..8db3fb876 100644 --- a/tangelo/toolboxes/measurements/tests/test_qubit_terms_grouping.py +++ b/tangelo/toolboxes/measurements/tests/test_qubit_terms_grouping.py @@ -17,7 +17,7 @@ from openfermion import load_operator from tangelo.linq import translator, Simulator, Circuit -from tangelo.helpers import string_ham_to_of, measurement_basis_gates +from tangelo.helpers import measurement_basis_gates from tangelo.toolboxes.measurements import group_qwc, exp_value_from_measurement_bases path_data = os.path.dirname(os.path.abspath(__file__)) + '/data' From 91ba396305a0193499d06181559ad2f43518ea92 Mon Sep 17 00:00:00 2001 From: AlexandreF-1qbit <76115575+AlexandreF-1qbit@users.noreply.github.com> Date: Sun, 23 Jan 2022 15:57:04 -0500 Subject: [PATCH 60/68] Fix for issue 98 (#116) * Attempt to fix #98. Co-authored-by: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> --- .../qpu_connection/qemist_cloud_connection.py | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/tangelo/linq/qpu_connection/qemist_cloud_connection.py b/tangelo/linq/qpu_connection/qemist_cloud_connection.py index 9dac4aaf2..9766eae3d 100644 --- a/tangelo/linq/qpu_connection/qemist_cloud_connection.py +++ b/tangelo/linq/qpu_connection/qemist_cloud_connection.py @@ -45,8 +45,11 @@ def job_submit(circuit, n_shots, backend): job_options = {'shots': n_shots, 'backend': backend} # Submit the problem - qemist_cloud_job_id = util.solve_quantum_circuits_async(serialized_fragment=circuit_data, - serialized_solver=job_options)[0] + try: + qemist_cloud_job_id = util.solve_quantum_circuits_async(serialized_fragment=circuit_data, + serialized_solver=job_options)[0] + except NameError: + raise ModuleNotFoundError("job_submit function needs qemist_client.util module.") return qemist_cloud_job_id @@ -61,7 +64,12 @@ def job_status(qemist_cloud_job_id): Returns: str: current status of the problem, as a string. """ - return util.get_problem_status(qemist_cloud_job_id) + try: + res = util.get_problem_status(qemist_cloud_job_id) + except NameError: + raise ModuleNotFoundError("job_status function needs qemist_client.util module.") + + return res def job_cancel(qemist_cloud_job_id): @@ -75,9 +83,11 @@ def job_cancel(qemist_cloud_job_id): Returns: dict: cancelled problems / subproblems. """ - - res = util.cancel_problems(qemist_cloud_job_id) - # TODO: If res is coming out as an error code, Tangelo should raise an error + try: + res = util.cancel_problems(qemist_cloud_job_id) + except NameError: + raise ModuleNotFoundError("job_cancel function needs qemist_client.util module.") + # TODO: If res is coming out as an error code, we should raise an error return res @@ -92,12 +102,15 @@ def job_result(qemist_cloud_job_id): Returns: dict: Histogram of measurement frequencies. - dict): The cloud provider raw data. + dict: The cloud provider raw data. """ try: util.monitor_problem_status(problem_handle=qemist_cloud_job_id, verbose=False) + except NameError: + raise ModuleNotFoundError("job_result function needs qemist_client.util module.") + except KeyboardInterrupt: print(f"\nYour problem is still running with id {qemist_cloud_job_id}.\n") command = input("Type 'cancel' and return to cancel your problem." @@ -115,7 +128,8 @@ def job_result(qemist_cloud_job_id): f"Reconnect and block until the problem is complete with qemist_client.util.monitor_problem_status({qemist_cloud_job_id}).\n\n") raise - # Once a result is available, retrieve it + # Once a result is available, retrieve it. + # If the util module is not found earlier, an error has been raised. output = util.get_quantum_results(problem_handle=qemist_cloud_job_id)[qemist_cloud_job_id] # Amazon Braket: parsing of output From ae3a59c5579b923c0d813e024ea154cd3468c56d Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Mon, 24 Jan 2022 13:47:13 -0800 Subject: [PATCH 61/68] Copy gate data instead of referencing it when instantiation Circuit object (#118) * Modifying external gate-objects that were provided as input to build a Circuit no longer modifies the Circuit after it's been built. --- tangelo/linq/circuit.py | 7 ++++--- tangelo/linq/tests/test_circuits.py | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/tangelo/linq/circuit.py b/tangelo/linq/circuit.py index 895acb901..dad128735 100644 --- a/tangelo/linq/circuit.py +++ b/tangelo/linq/circuit.py @@ -128,13 +128,14 @@ def is_mixed_state(self): """ return "MEASURE" in self.counts - def add_gate(self, gate): + def add_gate(self, g): """Add a new gate to a circuit object and update other fields of the circuit object to gradually keep track of its properties (gate count, qubit indices...). """ - # Add the gate to the list of gates - self._gates += [gate] + # Add a copy of the gate to the list of gates + gate = Gate(g.name, g.target, g.control, g.parameter, g.is_variational) + self._gates.append(gate) # A circuit is variational as soon as a variational gate is added to it if gate.is_variational: diff --git a/tangelo/linq/tests/test_circuits.py b/tangelo/linq/tests/test_circuits.py index 708e25795..591fbdfdf 100644 --- a/tangelo/linq/tests/test_circuits.py +++ b/tangelo/linq/tests/test_circuits.py @@ -59,6 +59,21 @@ def test_is_variational(self): self.assertTrue(circuit1.is_variational is False) self.assertTrue(circuit3.is_variational is True) + def test_gate_data_is_copied(self): + """ Ensure that circuit is not referencing mutable variables that could cause it to change after + instantiation if the values of the variables are later changed in external code. """ + + mygates2 = copy.deepcopy(mygates) + c1 = Circuit(mygates2) + + g = mygates2[0] + g.target.append(1) + g.name = 'POTATO' + g.parameter = -999. + + c2 = Circuit(mygates2) + self.assertTrue(c1 != c2) + def test_width(self): """ Ensure the width attribute of the circuit object (number of qubits) matches the gate operations present in the circuit. """ From f1530e73fd5baf69b43bf17b2e34ad8594d7291f Mon Sep 17 00:00:00 2001 From: Rudi Plesch Date: Mon, 7 Feb 2022 10:56:31 -0800 Subject: [PATCH 62/68] Fixed QEMIST Cloud QPU connection ctrl-c in job_result. (#121) --- tangelo/linq/qpu_connection/qemist_cloud_connection.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tangelo/linq/qpu_connection/qemist_cloud_connection.py b/tangelo/linq/qpu_connection/qemist_cloud_connection.py index 9766eae3d..168849bdd 100644 --- a/tangelo/linq/qpu_connection/qemist_cloud_connection.py +++ b/tangelo/linq/qpu_connection/qemist_cloud_connection.py @@ -116,11 +116,12 @@ def job_result(qemist_cloud_job_id): command = input("Type 'cancel' and return to cancel your problem." "Type anything else to disconnect but keep the problem running.\n") if command.lower() == "cancel": - ret = job_cancel() + ret = job_cancel(qemist_cloud_job_id) print("Problem cancelled.", ret) else: print(f"Reconnect and block until the problem is complete with " f"qemist_client.util.monitor_problem_status({qemist_cloud_job_id}).\n\n") + raise except Exception: print(f"\n\nYour problem is still running with handle {qemist_cloud_job_id}.\n" From dc707835c71802c175846e21e7355fa8ea3e8f08 Mon Sep 17 00:00:00 2001 From: Rudi Plesch Date: Tue, 8 Feb 2022 15:39:48 -0800 Subject: [PATCH 63/68] Estimate QPU cost with QEMIST Cloud API. (#120) * Estimate QPU cost with QEMIST Cloud API. * Optional backend parameters for job_estimate. Co-authored-by: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> --- .../qpu_connection/qemist_cloud_connection.py | 60 ++++++++----------- 1 file changed, 25 insertions(+), 35 deletions(-) diff --git a/tangelo/linq/qpu_connection/qemist_cloud_connection.py b/tangelo/linq/qpu_connection/qemist_cloud_connection.py index 168849bdd..c7e2f6544 100644 --- a/tangelo/linq/qpu_connection/qemist_cloud_connection.py +++ b/tangelo/linq/qpu_connection/qemist_cloud_connection.py @@ -45,12 +45,8 @@ def job_submit(circuit, n_shots, backend): job_options = {'shots': n_shots, 'backend': backend} # Submit the problem - try: - qemist_cloud_job_id = util.solve_quantum_circuits_async(serialized_fragment=circuit_data, - serialized_solver=job_options)[0] - except NameError: - raise ModuleNotFoundError("job_submit function needs qemist_client.util module.") - + qemist_cloud_job_id = util.solve_quantum_circuits_async(serialized_fragment=circuit_data, + serialized_solver=job_options)[0] return qemist_cloud_job_id @@ -64,18 +60,14 @@ def job_status(qemist_cloud_job_id): Returns: str: current status of the problem, as a string. """ - try: - res = util.get_problem_status(qemist_cloud_job_id) - except NameError: - raise ModuleNotFoundError("job_status function needs qemist_client.util module.") + res = util.get_problem_status(qemist_cloud_job_id) return res def job_cancel(qemist_cloud_job_id): """Cancels the job matching the input job id, if done in time before it - starts. Returns a list of cancelled problems and number of subproblems, if - any. + starts. Args: qemist_cloud_job_id (int): problem handle / job identifier. @@ -83,10 +75,7 @@ def job_cancel(qemist_cloud_job_id): Returns: dict: cancelled problems / subproblems. """ - try: - res = util.cancel_problems(qemist_cloud_job_id) - except NameError: - raise ModuleNotFoundError("job_cancel function needs qemist_client.util module.") + res = util.cancel_problems(qemist_cloud_job_id) # TODO: If res is coming out as an error code, we should raise an error return res @@ -108,9 +97,6 @@ def job_result(qemist_cloud_job_id): try: util.monitor_problem_status(problem_handle=qemist_cloud_job_id, verbose=False) - except NameError: - raise ModuleNotFoundError("job_result function needs qemist_client.util module.") - except KeyboardInterrupt: print(f"\nYour problem is still running with id {qemist_cloud_job_id}.\n") command = input("Type 'cancel' and return to cancel your problem." @@ -140,31 +126,35 @@ def job_result(qemist_cloud_job_id): return freqs, raw_data -def job_estimate(circuit, n_shots): - """Returns an estimate of the cost of running an experiment. Some service - providers care about the complexity / structure of the input quantum - circuit, some do not. - - Some backends may charge per minute (such as simulators), which is difficult - to estimate and may be misleading. They are currently not included. +def job_estimate(circuit, n_shots, backend=None): + """Returns an estimate of the cost of running an experiment, for a specified backend + or all backends available. Some service providers care about the + complexity / structure of the input quantum circuit, some do not. - Braket prices: https://aws.amazon.com/braket/pricing/ - Azure Quantum prices: TBD + The backend identifier strings that a user can provide as argument can be obtained + by calling this function without specifying a backend. They appear as keys in + the returned dictionary. These strings may change with time, as we adjust to the + growing cloud quantum offer (services and devices). Args: circuit (Circuit): the abstract circuit to be run on the target device. n_shots (int): number of shots in the expriment. + backend (str): the identifier string for the desired backend. Returns: - dict: A dictionary of floating-point values (prices) in USD. + dict: Returns dict of prices in USD. If backend is not None, dictionary + contains the cost for running the desired job. If backend is None, + returns dictionary of prices for all supported backends. """ - # Compute prices for each available backend (see provider formulas) - price_estimate = dict() - price_estimate['braket_ionq'] = 0.3 + 0.01 * n_shots - price_estimate['braket_rigetti'] = 0.3 + 0.00035 * n_shots + # Serialize circuit data + circuit_data = circuit.serialize() + + # Build option dictionary + job_options = {'shots': n_shots} + if backend: + job_options['backend'] = backend - # Round up to a cent for readability - price_estimate = {k: round(v, 2) for k, v in price_estimate.items()} + price_estimate = util.check_qpu_cost(circuit_data, job_options) return price_estimate From 89708f2678acb0d4024ac90f99ae800db5dfda50 Mon Sep 17 00:00:00 2001 From: AlexandreF-1qbit <76115575+AlexandreF-1qbit@users.noreply.github.com> Date: Fri, 11 Feb 2022 04:09:37 -0500 Subject: [PATCH 64/68] Improvements for handling exp data with ClassicalShadow (#124) * General improvements for CS, mainly randomized. --- .../classical_shadows/adaptive.py | 2 +- .../classical_shadows/classical_shadows.py | 10 ++++++++-- .../classical_shadows/derandomized.py | 2 +- .../classical_shadows/randomized.py | 19 ++++++++++++++++++- .../tests/test_adaptive_classical_shadows.py | 7 +++++++ .../test_derandomized_classical_shadows.py | 7 +++++++ .../test_randomized_classical_shadows.py | 15 +++++++++++---- 7 files changed, 53 insertions(+), 9 deletions(-) diff --git a/tangelo/toolboxes/measurements/classical_shadows/adaptive.py b/tangelo/toolboxes/measurements/classical_shadows/adaptive.py index 7e7e70223..601e8a2b3 100644 --- a/tangelo/toolboxes/measurements/classical_shadows/adaptive.py +++ b/tangelo/toolboxes/measurements/classical_shadows/adaptive.py @@ -46,7 +46,7 @@ def build(self, n_shots, qu_op): measurement_procedure = [self._choose_measurement(qu_op) for _ in range(n_shots)] - self.unitaries = measurement_procedure + self.unitaries += measurement_procedure return measurement_procedure def _choose_measurement(self, qu_op): diff --git a/tangelo/toolboxes/measurements/classical_shadows/classical_shadows.py b/tangelo/toolboxes/measurements/classical_shadows/classical_shadows.py index b620e63ca..6d6000e7f 100644 --- a/tangelo/toolboxes/measurements/classical_shadows/classical_shadows.py +++ b/tangelo/toolboxes/measurements/classical_shadows/classical_shadows.py @@ -29,7 +29,7 @@ class ClassicalShadow(abc.ABC): with the fewest measurement possible. """ - def __init__(self, circuit, bitstrings=None, unitaries=None): + def __init__(self, circuit=None, bitstrings=None, unitaries=None): """Default constructor for the ClassicalShadow object. This class is the parent class for the different classical shadows flavors. The object is defined by the bistrings and unitaries used in the process. Abstract @@ -37,6 +37,7 @@ def __init__(self, circuit, bitstrings=None, unitaries=None): channel. Args: + circuit (Circuit): State to characterize. bistrings (list of str): Representation of the outcomes for all snapshots. E.g. ["11011", "10000", ...]. unitaries (list of str): Representation of the unitary for every @@ -47,13 +48,15 @@ def __init__(self, circuit, bitstrings=None, unitaries=None): self.bitstrings = list() if bitstrings is None else bitstrings self.unitaries = list() if unitaries is None else unitaries + assert len(self.bitstrings) == len(self.unitaries), f"bistrings and unitaries must be the same length." + # If the state has been estimated, it is stored into this attribute. self.state_estimate = None @property def n_qubits(self): """Returns the number of qubits the shadow represents.""" - return self.circuit.width + return self.circuit.width if self.circuit else len(self.bitstrings[0]) @property def size(self): @@ -114,6 +117,9 @@ def simulate(self, backend, initial_statevector=None): if not self.unitaries: raise ValueError(f"The build method of {self.__class__.__name__} must be called before simulation.") + if self.bitstrings: + raise NotImplementedError("Appending new simulation results to already defined self.bistrings is not implemented yet.") + if backend.n_shots != 1: warnings.warn(f"Changing number of shots to 1 for the backend (classical shadows).") backend.n_shots = 1 diff --git a/tangelo/toolboxes/measurements/classical_shadows/derandomized.py b/tangelo/toolboxes/measurements/classical_shadows/derandomized.py index d4d5a6851..a2b20ac1d 100644 --- a/tangelo/toolboxes/measurements/classical_shadows/derandomized.py +++ b/tangelo/toolboxes/measurements/classical_shadows/derandomized.py @@ -118,7 +118,7 @@ def build(self, n_shots, qu_op, eta=0.9): # Fill "missing" shots with a set of random (already chosen) basis. measurement_procedure += random.choices(measurement_procedure, k=n_shots-len(measurement_procedure)) - self.unitaries = measurement_procedure + self.unitaries += measurement_procedure return measurement_procedure def get_basis_circuits(self, only_unique=False): diff --git a/tangelo/toolboxes/measurements/classical_shadows/randomized.py b/tangelo/toolboxes/measurements/classical_shadows/randomized.py index a4a44ecb8..411ab819f 100644 --- a/tangelo/toolboxes/measurements/classical_shadows/randomized.py +++ b/tangelo/toolboxes/measurements/classical_shadows/randomized.py @@ -52,6 +52,23 @@ class RandomizedClassicalShadow(ClassicalShadow): \hat{\rho} = \bigotimes_{j=1}^n \left( 3U_j^{\dagger} |b_j\rangle \langle b_j| U_j - \mathbb{I} \right) """ + def __init__(self, circuit=None, bitstrings=None, unitaries=None, shuffle=True): + """Overloads the init method to shuffle the bistrings and unitaries if + those are provided. + + Args: + shuffle (bool): Randomize bitstrings and unitaries. Default = True. + """ + + super().__init__(circuit, bitstrings, unitaries) + + if bitstrings and shuffle: + # Shuffling the order while keeping the bistring to its unitary. + random_bitstrings = list(zip(bitstrings, unitaries)) + random.shuffle(random_bitstrings) + new_bistrings, new_unitaries = zip(*random_bitstrings) + self.bitstrings, self.unitaries = list(new_bistrings), list(new_unitaries) + def build(self, n_shots): """Random sampling of single pauli words. @@ -66,7 +83,7 @@ def build(self, n_shots): single_round_measurement = "".join([random.choice(["X", "Y", "Z"]) for _ in range(self.n_qubits)]) measurement_procedure.append(single_round_measurement) - self.unitaries = measurement_procedure + self.unitaries += measurement_procedure return measurement_procedure def get_basis_circuits(self, only_unique=False): diff --git a/tangelo/toolboxes/measurements/tests/test_adaptive_classical_shadows.py b/tangelo/toolboxes/measurements/tests/test_adaptive_classical_shadows.py index de1fab490..3f29550e5 100644 --- a/tangelo/toolboxes/measurements/tests/test_adaptive_classical_shadows.py +++ b/tangelo/toolboxes/measurements/tests/test_adaptive_classical_shadows.py @@ -51,6 +51,13 @@ def test_initialization(self): AdaptiveClassicalShadow(state, bitstrings, unitaries) + def test_same_length_check(self): + """Testing the case where arguments are not the same length.""" + + wrong_bitstrings = bitstrings + ["00"] + with self.assertRaises(AssertionError): + AdaptiveClassicalShadow(state, wrong_bitstrings, unitaries) + def test_shadow_properties(self): """Testing of the shadow properties.""" diff --git a/tangelo/toolboxes/measurements/tests/test_derandomized_classical_shadows.py b/tangelo/toolboxes/measurements/tests/test_derandomized_classical_shadows.py index 82fd7334e..b7be90721 100644 --- a/tangelo/toolboxes/measurements/tests/test_derandomized_classical_shadows.py +++ b/tangelo/toolboxes/measurements/tests/test_derandomized_classical_shadows.py @@ -42,6 +42,13 @@ def test_initialization(self): DerandomizedClassicalShadow(state, bitstrings, unitaries) + def test_same_length_check(self): + """Testing the case where arguments are not the same length.""" + + wrong_bitstrings = bitstrings + ["00"] + with self.assertRaises(AssertionError): + DerandomizedClassicalShadow(state, wrong_bitstrings, unitaries) + def test_shadow_properties(self): """Testing of the shadow properties.""" diff --git a/tangelo/toolboxes/measurements/tests/test_randomized_classical_shadows.py b/tangelo/toolboxes/measurements/tests/test_randomized_classical_shadows.py index 00932cc91..c333ff6f8 100644 --- a/tangelo/toolboxes/measurements/tests/test_randomized_classical_shadows.py +++ b/tangelo/toolboxes/measurements/tests/test_randomized_classical_shadows.py @@ -53,10 +53,17 @@ def test_initialization(self): RandomizedClassicalShadow(state, bitstrings, unitaries) + def test_same_length_check(self): + """Testing the case where arguments are not the same length.""" + + wrong_bitstrings = bitstrings + ["00"] + with self.assertRaises(AssertionError): + RandomizedClassicalShadow(state, wrong_bitstrings, unitaries) + def test_shadow_properties(self): """Testing of the shadow properties.""" - cs = RandomizedClassicalShadow(state, bitstrings, unitaries) + cs = RandomizedClassicalShadow(state, bitstrings, unitaries, shuffle=False) self.assertEqual(cs.n_qubits, 2) self.assertEqual(cs.size, 100) @@ -65,21 +72,21 @@ def test_shadow_properties(self): def test_get_term_observable(self): """Testing the computation of a single qubit term.""" - cs = RandomizedClassicalShadow(state, bitstrings, unitaries) + cs = RandomizedClassicalShadow(state, bitstrings, unitaries, shuffle=False) obs = cs.get_term_observable([(0, "Y"), (1, "Y")], 1., k=10) self.assertAlmostEqual(obs, -0.89999, places=4) def test_get_observable(self): """Testings the computation of an eigenvalue of a QubitOperator.""" - cs = RandomizedClassicalShadow(state, bitstrings, unitaries) + cs = RandomizedClassicalShadow(state, bitstrings, unitaries, shuffle=False) obs = cs.get_observable(QubitOperator("Y0 Y1", coefficient=1.)) self.assertAlmostEqual(obs, -0.89999, places=4) def test_estimate_state(self): """Testing of the state estimation method to get the density matrix.""" - cs = RandomizedClassicalShadow(state, bitstrings, unitaries) + cs = RandomizedClassicalShadow(state, bitstrings, unitaries, shuffle=False) rho_estimate = cs.estimate_state() # Previously ran with this specific shadow. From a739546d43d53bf73a61b86a33e4ec7985e749b2 Mon Sep 17 00:00:00 2001 From: JamesB-1qbit <84878946+JamesB-1qbit@users.noreply.github.com> Date: Fri, 11 Feb 2022 13:58:25 -0500 Subject: [PATCH 65/68] Qulacs operator build changed to fix memory leak (#122) * Workaround for memory leak in qulacs when building a qulacs operator --- tangelo/linq/simulator.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tangelo/linq/simulator.py b/tangelo/linq/simulator.py index 300f9c2ac..6bb48d077 100644 --- a/tangelo/linq/simulator.py +++ b/tangelo/linq/simulator.py @@ -340,14 +340,14 @@ def _get_expectation_value_from_statevector(self, qubit_operator, state_prep_cir if self._target == "qulacs" and not self.n_shots: import qulacs - op = qulacs.quantum_operator.create_quantum_operator_from_openfermion_text(qubit_operator.__repr__()) - if op.get_qubit_count() == n_qubits: - return op.get_expectation_value(self._current_state).real - else: - operator = qulacs.GeneralQuantumOperator(n_qubits) - for i in range(op.get_term_count()): - operator.add_operator(op.get_term(i)) - return operator.get_expectation_value(self._current_state).real + # Note: This section previously used qulacs.quantum_operator.create_quantum_operator_from_openfermion_text but was changed + # due to a memory leak. We can re-evaluate the implementation if/when Issue #303 (https://github.com/qulacs/qulacs/issues/303) + # is fixed. + operator = qulacs.Observable(n_qubits) + for term, coef in qubit_operator.terms.items(): + pauli_string = "".join(f" {op} {qu}" for qu, op in term) + operator.add_operator(coef, pauli_string) + return operator.get_expectation_value(self._current_state).real # Use cirq built-in expectation_from_state_vector/epectation_from_density_matrix # noise model would require From 3c941792243ec6bf0316a83d3615512cb1e610bf Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Tue, 15 Feb 2022 09:52:57 -0800 Subject: [PATCH 66/68] Prep release (#126) * Create_release_branch workflow added with CHANGELOGS.md, version number handed differently. --- .github/workflows/create_release_branch.yml | 46 +++++++++++++++++++++ CHANGELOGS.md | 16 +++++++ setup.py | 5 ++- tangelo/_version.py | 16 +++++++ 4 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/create_release_branch.yml create mode 100644 CHANGELOGS.md create mode 100644 tangelo/_version.py diff --git a/.github/workflows/create_release_branch.yml b/.github/workflows/create_release_branch.yml new file mode 100644 index 000000000..71ae6cc7b --- /dev/null +++ b/.github/workflows/create_release_branch.yml @@ -0,0 +1,46 @@ +name: Create Release Branch +on: + workflow_dispatch: + inputs: + versionName: + description: 'Name of version (ie 5.5.0)' + required: true +jobs: + createrelease: + runs-on: ubuntu-latest + + steps: + - name: Check out code + uses: actions/checkout@v2 + - name: Create release branch + run: git checkout -b release/v${{ github.event.inputs.versionName }} + - name: Initialize mandatory git config + run: | + git config user.name "GitHub Actions" + git config user.email noreply@github.com + - name: Change version number and name + run: sed -i 's/__version__ = .*/__version__ = "${{ github.event.inputs.versionName }}"/' tangelo/_version.py + - name: Update Changelog + uses: thomaseizinger/keep-a-changelog-new-release@v1 + with: + version: ${{ github.event.inputs.versionName }} + - name: Commit changelog and manifest files + id: make-commit + run: | + git add CHANGELOG.md + git commit --message "Prepare release ${{ github.event.inputs.versionName }}" + echo "::set-output name=commit::$(git rev-parse HEAD)" + - name: Push new branch + run: git push origin release/v${{ github.event.inputs.versionName }} + - name: Create pull request into main + uses: thomaseizinger/create-pull-request@1.0.0 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + head: release/v${{ github.event.inputs.versionName }} + base: main + title: v${{ github.event.inputs.versionName }} into main + reviewers: ${{ github.event.issue.user.login }} + body: | + This PR was created in response to "create_release_branch" workflow running. + I've updated the version name and code commit: ${{ steps.make-commit.outputs.commit }}. + Don't forget to update CHANGELOGS.md and then merge back main into develop after this PR goes through. diff --git a/CHANGELOGS.md b/CHANGELOGS.md new file mode 100644 index 000000000..aa09ec894 --- /dev/null +++ b/CHANGELOGS.md @@ -0,0 +1,16 @@ +# Changelog + +This file documents the main changes between versions of the code. + +## [Unreleased] + +### Added + + + +### Changed + + + +### Deprecated + diff --git a/setup.py b/setup.py index d653c1eb8..d5ef02243 100755 --- a/setup.py +++ b/setup.py @@ -7,6 +7,9 @@ def install(package): subprocess.check_call([sys.executable, "-m", "pip", "install", package]) +with open("tangelo/_version.py") as f: + version = f.readlines()[-1].split()[-1].strip("\"'") + with open('README.rst', 'r') as f: long_description = f.read() @@ -17,7 +20,7 @@ def install(package): setuptools.setup( name="tangelo", author="The Tangelo developers", - version="0.3.0", + version=version, description="Tangelo is a python package developed by Good Chemistry Company, focusing on the development " "of end-to-end materials simulation workflows on quantum computers.", long_description=long_description, diff --git a/tangelo/_version.py b/tangelo/_version.py new file mode 100644 index 000000000..0da3e0b76 --- /dev/null +++ b/tangelo/_version.py @@ -0,0 +1,16 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" Version number (major.minor.patch[-label]) """ +__version__ = "0.3.0-pr" From c2b7cab44fe6ffdf6567cd8400608be67da58266 Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Tue, 15 Feb 2022 11:03:15 -0800 Subject: [PATCH 67/68] indentation error fixed --- .github/workflows/create_release_branch.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/create_release_branch.yml b/.github/workflows/create_release_branch.yml index 71ae6cc7b..6ba95ae75 100644 --- a/.github/workflows/create_release_branch.yml +++ b/.github/workflows/create_release_branch.yml @@ -35,7 +35,7 @@ jobs: - name: Create pull request into main uses: thomaseizinger/create-pull-request@1.0.0 with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} head: release/v${{ github.event.inputs.versionName }} base: main title: v${{ github.event.inputs.versionName }} into main From 32d64fe9c59441f3158942c61beac031d18f3120 Mon Sep 17 00:00:00 2001 From: ValentinS4t1qbit <41597680+ValentinS4t1qbit@users.noreply.github.com> Date: Tue, 15 Feb 2022 11:07:49 -0800 Subject: [PATCH 68/68] Rename CHANGELOGS.md to CHANGELOG.md --- CHANGELOGS.md => CHANGELOG.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename CHANGELOGS.md => CHANGELOG.md (100%) diff --git a/CHANGELOGS.md b/CHANGELOG.md similarity index 100% rename from CHANGELOGS.md rename to CHANGELOG.md