diff --git a/.gitignore b/.gitignore index c9ace05..285bc36 100644 --- a/.gitignore +++ b/.gitignore @@ -70,3 +70,5 @@ ENV/ # mkdocs documentation /site + +*.lprof diff --git a/.travis.yml b/.travis.yml index b236c50..78dd0a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,7 @@ matrix: apt: sources: ['ubuntu-toolchain-r-test'] packages: ['gcc-4.9', 'g++-4.9'] - env: CC=gcc-4.9 CXX=g++-4.9 PYTHON=3.5 + env: CC=gcc-4.9 CXX=g++-4.9 PYTHON=3.6 before_install: - pip$PY install --upgrade pip setuptools wheel diff --git a/README.rst b/README.rst index b9bd29f..23b1aa8 100644 --- a/README.rst +++ b/README.rst @@ -20,7 +20,12 @@ Getting started --------------- To start using FermiLib, simply follow the installation instructions in the `intro `__. There, you will also find `code examples `__. Also, make sure to check out the `ProjectQ -website `__ and the detailed `code documentation `__. Moreover, take a look at the available `plugins `__ for FermiLib. +website `__ and the detailed `code documentation `__. Moreover, take a look at the available plugins for FermiLib. + +Plugins +------- + +In order to generate molecular hamiltonians in Gaussian basis sets and perform other complicated electronic structure calculations, one can install plugins. We currently support Psi4 (plugin `here `__, recommended) and PySCF (plugin `here `__). How to contribute ----------------- @@ -41,6 +46,11 @@ Authors The first release of FermiLib (v0.1a0) was developed by `Ryan Babbush `__, `Jarrod McClean `__, `Damian S. Steiger `__, `Ian D. Kivlichan `__, `Thomas Häner `__, `Vojtech Havlicek `__, `Matthew Neeley `__, and `Wei Sun `__. +Questions? +---------- + +If you have any other questions, please contact fermilib@projectq.ch. + License ------- diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..ce4007b --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,12 @@ +# Dockerfile for FermiLib+ProjectQ + +# Change the following line to "FROM continuumio/anaconda" to use Python 2 +FROM continuumio/anaconda3 + +USER root + +RUN apt install -y g++ + +RUN pip install git+https://github.com/ProjectQ-Framework/ProjectQ.git && \ + pip install git+https://github.com/ProjectQ-Framework/FermiLib.git + diff --git a/docker/README.rst b/docker/README.rst new file mode 100644 index 0000000..814bc7e --- /dev/null +++ b/docker/README.rst @@ -0,0 +1,30 @@ +Docker Setup for FermiLib + ProjectQ +==================================== + +This Docker image will help users to easily install `FermiLib `__ and `ProjectQ `__. Check out Docker's `website `__ that describes what a container image is and why it can be so useful. + +What is included? +----------------- + +- Conda Python 3 (but you can also use Python 2 with one minor change in the Dockerfile. See the Dockerfile for instructions.) +- `ProjectQ `__ +- `FermiLib `__ + +How to use it? +-------------- + +1. To use this image, you first need to install `Docker `__. + +2. To build the Docker image, move the Dockerfile to your working directory. Then execute: + +.. code-block:: bash + + docker build -t "fermiq_docker" . + +3. To run the image (assuming you're still inside your working directory), execute with :code:`YOUR_WORK_DIR` as the path to your working directory: + +.. code-block:: bash + + docker run -it -v $(pwd):YOUR_WORK_DIR -w YOUR_WORK_DIR fermiq_docker + +When you're done using the Docker image, you can execute :code:`docker stop YOUR_CONTAINER_ID` or :code:`docker kill YOUR_CONTAINER_ID` to stop your container (you can get the container ID by using the command :code:`docker ps`). Finally, feel free to use this as a parent image to build a more customized image layer, perhaps containing the available plugins (`PySCF `__ or `Psi4 `__) for FermiLib. diff --git a/docs/intro.rst b/docs/intro.rst index 5dd62a9..c63ab73 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -57,3 +57,8 @@ To see a basic example with both fermionic and qubit operators as well as whethe This code creates the fermionic operator :math:`a^\dagger_2 a_0` and adds its Hermitian conjugate :math:`a^\dagger_0 a_2` to it. It then maps the resulting fermionic operator to qubit operators using two transforms included in FermiLib, the Jordan-Wigner and Bravyi-Kitaev transforms. Despite the different representations, these operators are iso-spectral. The example also shows some of the intuitive string methods included in FermiLib. Further examples can be found in the docs (`Examples` in the panel on the left) and in the FermiLib examples folder on `GitHub `_. + +Plugins +------- + +In order to generate molecular hamiltonians in Gaussian basis sets and perform other complicated electronic structure calculations, one can install plugins. We currently support Psi4 (plugin `here `__, recommended) and PySCF (plugin `here `__). diff --git a/examples/fermilib_demo.ipynb b/examples/fermilib_demo.ipynb index 458bfb5..af134fd 100644 --- a/examples/fermilib_demo.ipynb +++ b/examples/fermilib_demo.ipynb @@ -55,7 +55,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 1, "metadata": {}, "outputs": [ { @@ -86,7 +86,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 2, "metadata": {}, "outputs": [ { @@ -123,7 +123,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -151,7 +151,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -190,7 +190,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -244,7 +244,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -297,7 +297,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -335,7 +335,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -389,7 +389,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -447,7 +447,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -586,7 +586,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 11, "metadata": {}, "outputs": [ { @@ -615,8 +615,8 @@ "-3.2898681337 Y0 Z1 Y2 +\n", "-3.2898681337 X0 X1\n", "\n", - "[ 3.45556916e-15 3.59434704e-15 3.55271368e-15 -7.10542736e-15\n", - " 7.10542736e-15 -7.10542736e-15 -7.10542736e-15 -7.10542736e-15]\n" + "[ 6.98052727e-15 6.25888230e-15 1.42108547e-14 3.55271368e-15\n", + " 1.77635684e-14 7.10542736e-15 0.00000000e+00 1.42108547e-14]\n" ] } ], @@ -659,12 +659,12 @@ "\n", "When electronic structure calculations are run, the data files for the molecule can be automatically updated. If one wishes to later use that data they either initialize MolecularData with the instance filename or initialize the instance and then later call the .load() method.\n", "\n", - "Basis functions are provided to initialization using a string such as \"6-31g\". Geometries can be specified using a simple txt input file (see geometry_from_file function in molecular_data.py) or can be passed using a simple python list format demonstrated below. Atoms are specified using a string for their atomic symbol. Distances should be provided in atomic units (Bohr). Below we initialize a simple instance of MolecularData without performing any electronic structure calculations." + "Basis functions are provided to initialization using a string such as \"6-31g\". Geometries can be specified using a simple txt input file (see geometry_from_file function in molecular_data.py) or can be passed using a simple python list format demonstrated below. Atoms are specified using a string for their atomic symbol. Distances should be provided in angstrom. Below we initialize a simple instance of MolecularData without performing any electronic structure calculations." ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -673,7 +673,7 @@ "text": [ "Molecule has automatically generated name H2_sto-3g_singlet_0.7414\n", "Information about this molecule would be saved at:\n", - "/Users/Lappy/Dropbox/Programming/fermilib/src/fermilib/data/H2_sto-3g_singlet_0.7414\n", + "/usr/local/google/home/babbush/FermiLib/src/fermilib/data/H2_sto-3g_singlet_0.7414\n", "\n", "This molecule has 2 atoms and 2 electrons.\n", "Contains H atom, which has 1 protons.\n", @@ -715,7 +715,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 13, "metadata": {}, "outputs": [ { @@ -724,7 +724,7 @@ "text": [ "0.3\n", "\n", - "At bond length of 0.3 Bohr, molecular hydrogen has:\n", + "At bond length of 0.3 angstrom, molecular hydrogen has:\n", "Hartree-Fock energy of -0.593827764585 Hartree.\n", "MP2 energy of -0.599781278607 Hartree.\n", "FCI energy of -0.601803716835 Hartree.\n", @@ -733,34 +733,34 @@ "Spatial orbital 1 has energy of 1.36893879525 Hartree.\n", "0.4\n", "\n", - "At bond length of 0.4 Bohr, molecular hydrogen has:\n", + "At bond length of 0.4 angstrom, molecular hydrogen has:\n", "Hartree-Fock energy of -0.904361397714 Hartree.\n", - "MP2 energy of -0.911435297618 Hartree.\n", + "MP2 energy of -0.9114367297 Hartree.\n", "FCI energy of -0.914149708214 Hartree.\n", "Nuclear repulsion energy between protons is 1.32294302147 Hartree.\n", "Spatial orbital 0 has energy of -0.745212533291 Hartree.\n", "Spatial orbital 1 has energy of 1.16741639504 Hartree.\n", "0.5\n", "\n", - "At bond length of 0.5 Bohr, molecular hydrogen has:\n", - "Hartree-Fock energy of -1.04299627651 Hartree.\n", - "MP2 energy of -1.05148447259 Hartree.\n", - "FCI energy of -1.0551597965 Hartree.\n", - "Nuclear repulsion energy between protons is 1.05835441718 Hartree.\n", - "Spatial orbital 0 has energy of -0.690822327512 Hartree.\n", - "Spatial orbital 1 has energy of 0.988673669374 Hartree.\n", + "At bond length of 0.5 angstrom, molecular hydrogen has:\n", + "Hartree-Fock energy of -1.04299627454 Hartree.\n", + "MP2 energy of -1.05148606962 Hartree.\n", + "FCI energy of -1.05515979447 Hartree.\n", + "Nuclear repulsion energy between protons is 1.05835442184 Hartree.\n", + "Spatial orbital 0 has energy of -0.690822328662 Hartree.\n", + "Spatial orbital 1 has energy of 0.988673673002 Hartree.\n", "0.6\n", "\n", - "At bond length of 0.6 Bohr, molecular hydrogen has:\n", + "At bond length of 0.6 angstrom, molecular hydrogen has:\n", "Hartree-Fock energy of -1.10112824314 Hartree.\n", - "MP2 energy of -1.11133056631 Hartree.\n", + "MP2 energy of -1.11133176748 Hartree.\n", "FCI energy of -1.11628600783 Hartree.\n", "Nuclear repulsion energy between protons is 0.881962014317 Hartree.\n", "Spatial orbital 0 has energy of -0.640876264399 Hartree.\n", "Spatial orbital 1 has energy of 0.838084978149 Hartree.\n", "0.7\n", "\n", - "At bond length of 0.7 Bohr, molecular hydrogen has:\n", + "At bond length of 0.7 angstrom, molecular hydrogen has:\n", "Hartree-Fock energy of -1.11734903507 Hartree.\n", "MP2 energy of -1.12958030128 Hartree.\n", "FCI energy of -1.13618945427 Hartree.\n", @@ -769,16 +769,16 @@ "Spatial orbital 1 has energy of 0.714165281005 Hartree.\n", "0.8\n", "\n", - "At bond length of 0.8 Bohr, molecular hydrogen has:\n", + "At bond length of 0.8 angstrom, molecular hydrogen has:\n", "Hartree-Fock energy of -1.11085039699 Hartree.\n", - "MP2 energy of -1.12545166123 Hartree.\n", + "MP2 energy of -1.12545309198 Hartree.\n", "FCI energy of -1.13414766636 Hartree.\n", "Nuclear repulsion energy between protons is 0.661471510737 Hartree.\n", "Spatial orbital 0 has energy of -0.554495879764 Hartree.\n", "Spatial orbital 1 has energy of 0.612618083493 Hartree.\n", "0.9\n", "\n", - "At bond length of 0.9 Bohr, molecular hydrogen has:\n", + "At bond length of 0.9 angstrom, molecular hydrogen has:\n", "Hartree-Fock energy of -1.09191404011 Hartree.\n", "MP2 energy of -1.10926965709 Hartree.\n", "FCI energy of -1.12056028062 Hartree.\n", @@ -787,34 +787,34 @@ "Spatial orbital 1 has energy of 0.528477243161 Hartree.\n", "1.0\n", "\n", - "At bond length of 1.0 Bohr, molecular hydrogen has:\n", + "At bond length of 1.0 angstrom, molecular hydrogen has:\n", "Hartree-Fock energy of -1.06610864808 Hartree.\n", - "MP2 energy of -1.08666270096 Hartree.\n", + "MP2 energy of -1.08666480357 Hartree.\n", "FCI energy of -1.1011503293 Hartree.\n", "Nuclear repulsion energy between protons is 0.52917720859 Hartree.\n", "Spatial orbital 0 has energy of -0.484441678962 Hartree.\n", "Spatial orbital 1 has energy of 0.457501936164 Hartree.\n", "1.1\n", "\n", - "At bond length of 1.1 Bohr, molecular hydrogen has:\n", - "Hartree-Fock energy of -1.03653887354 Hartree.\n", - "MP2 energy of -1.06080311143 Hartree.\n", - "FCI energy of -1.07919294388 Hartree.\n", - "Nuclear repulsion energy between protons is 0.481070189627 Hartree.\n", - "Spatial orbital 0 has energy of -0.454218692719 Hartree.\n", - "Spatial orbital 1 has energy of 0.396695908412 Hartree.\n", + "At bond length of 1.1 angstrom, molecular hydrogen has:\n", + "Hartree-Fock energy of -1.03653887503 Hartree.\n", + "MP2 energy of -1.0608064532 Hartree.\n", + "FCI energy of -1.07919294497 Hartree.\n", + "Nuclear repulsion energy between protons is 0.481070191745 Hartree.\n", + "Spatial orbital 0 has energy of -0.454218694119 Hartree.\n", + "Spatial orbital 1 has energy of 0.396695911145 Hartree.\n", "1.2\n", "\n", - "At bond length of 1.2 Bohr, molecular hydrogen has:\n", + "At bond length of 1.2 angstrom, molecular hydrogen has:\n", "Hartree-Fock energy of -1.00510670488 Hartree.\n", - "MP2 energy of -1.03365866062 Hartree.\n", + "MP2 energy of -1.03366209661 Hartree.\n", "FCI energy of -1.05674074513 Hartree.\n", "Nuclear repulsion energy between protons is 0.440981007158 Hartree.\n", "Spatial orbital 0 has energy of -0.426502640723 Hartree.\n", "Spatial orbital 1 has energy of 0.344126878784 Hartree.\n", "1.3\n", "\n", - "At bond length of 1.3 Bohr, molecular hydrogen has:\n", + "At bond length of 1.3 angstrom, molecular hydrogen has:\n", "Hartree-Fock energy of -0.973110613949 Hartree.\n", "MP2 energy of -1.00658581984 Hartree.\n", "FCI energy of -1.03518626525 Hartree.\n", @@ -823,16 +823,16 @@ "Spatial orbital 1 has energy of 0.298513889839 Hartree.\n", "1.4\n", "\n", - "At bond length of 1.4 Bohr, molecular hydrogen has:\n", + "At bond length of 1.4 angstrom, molecular hydrogen has:\n", "Hartree-Fock energy of -0.941480652784 Hartree.\n", - "MP2 energy of -0.980563286065 Hartree.\n", + "MP2 energy of -0.980568732859 Hartree.\n", "FCI energy of -1.01546824814 Hartree.\n", "Nuclear repulsion energy between protons is 0.377983720421 Hartree.\n", "Spatial orbital 0 has energy of -0.377322823969 Hartree.\n", "Spatial orbital 1 has energy of 0.258901972313 Hartree.\n", "1.5\n", "\n", - "At bond length of 1.5 Bohr, molecular hydrogen has:\n", + "At bond length of 1.5 angstrom, molecular hydrogen has:\n", "Hartree-Fock energy of -0.910873552617 Hartree.\n", "MP2 energy of -0.956287593623 Hartree.\n", "FCI energy of -0.998149352414 Hartree.\n", @@ -841,34 +841,34 @@ "Spatial orbital 1 has energy of 0.224495437285 Hartree.\n", "1.6\n", "\n", - "At bond length of 1.6 Bohr, molecular hydrogen has:\n", + "At bond length of 1.6 angstrom, molecular hydrogen has:\n", "Hartree-Fock energy of -0.881732447952 Hartree.\n", - "MP2 energy of -0.934233322268 Hartree.\n", + "MP2 energy of -0.934241169572 Hartree.\n", "FCI energy of -0.983472728093 Hartree.\n", "Nuclear repulsion energy between protons is 0.330735755369 Hartree.\n", "Spatial orbital 0 has energy of -0.335296350589 Hartree.\n", "Spatial orbital 1 has energy of 0.1945979554 Hartree.\n", "1.7\n", "\n", - "At bond length of 1.7 Bohr, molecular hydrogen has:\n", - "Hartree-Fock energy of -0.854337624971 Hartree.\n", - "MP2 energy of -0.914703457885 Hartree.\n", - "FCI energy of -0.971426687651 Hartree.\n", - "Nuclear repulsion energy between protons is 0.311280710935 Hartree.\n", - "Spatial orbital 0 has energy of -0.316686044101 Hartree.\n", - "Spatial orbital 1 has energy of 0.168597320729 Hartree.\n", + "At bond length of 1.7 angstrom, molecular hydrogen has:\n", + "Hartree-Fock energy of -0.854337626951 Hartree.\n", + "MP2 energy of -0.914713958611 Hartree.\n", + "FCI energy of -0.971426688458 Hartree.\n", + "Nuclear repulsion energy between protons is 0.311280712306 Hartree.\n", + "Spatial orbital 0 has energy of -0.316686045438 Hartree.\n", + "Spatial orbital 1 has energy of 0.168597322543 Hartree.\n", "1.8\n", "\n", - "At bond length of 1.8 Bohr, molecular hydrogen has:\n", + "At bond length of 1.8 angstrom, molecular hydrogen has:\n", "Hartree-Fock energy of -0.828848145985 Hartree.\n", - "MP2 energy of -0.897869971342 Hartree.\n", + "MP2 energy of -0.897880538041 Hartree.\n", "FCI energy of -0.96181695212 Hartree.\n", "Nuclear repulsion energy between protons is 0.293987338106 Hartree.\n", "Spatial orbital 0 has energy of -0.299563220223 Hartree.\n", "Spatial orbital 1 has energy of 0.145960296625 Hartree.\n", "1.9\n", "\n", - "At bond length of 1.9 Bohr, molecular hydrogen has:\n", + "At bond length of 1.9 angstrom, molecular hydrogen has:\n", "Hartree-Fock energy of -0.805332843009 Hartree.\n", "MP2 energy of -0.883803751465 Hartree.\n", "FCI energy of -0.954338853453 Hartree.\n", @@ -877,16 +877,16 @@ "Spatial orbital 1 has energy of 0.126226048919 Hartree.\n", "2.0\n", "\n", - "At bond length of 2.0 Bohr, molecular hydrogen has:\n", + "At bond length of 2.0 angstrom, molecular hydrogen has:\n", "Hartree-Fock energy of -0.783792652466 Hartree.\n", - "MP2 energy of -0.872496424922 Hartree.\n", + "MP2 energy of -0.872510060481 Hartree.\n", "FCI energy of -0.948641111742 Hartree.\n", "Nuclear repulsion energy between protons is 0.264588604295 Hartree.\n", "Spatial orbital 0 has energy of -0.269459222444 Hartree.\n", "Spatial orbital 1 has energy of 0.108997368132 Hartree.\n", "2.1\n", "\n", - "At bond length of 2.1 Bohr, molecular hydrogen has:\n", + "At bond length of 2.1 angstrom, molecular hydrogen has:\n", "Hartree-Fock energy of -0.76417764989 Hartree.\n", "MP2 energy of -0.863877639601 Hartree.\n", "FCI energy of -0.944374680781 Hartree.\n", @@ -895,7 +895,7 @@ "Spatial orbital 1 has energy of 0.0939315946348 Hartree.\n", "2.2\n", "\n", - "At bond length of 2.2 Bohr, molecular hydrogen has:\n", + "At bond length of 2.2 angstrom, molecular hydrogen has:\n", "Hartree-Fock energy of -0.746401348355 Hartree.\n", "MP2 energy of -0.857830225327 Hartree.\n", "FCI energy of -0.941224033433 Hartree.\n", @@ -904,16 +904,16 @@ "Spatial orbital 1 has energy of 0.0807327078978 Hartree.\n", "2.3\n", "\n", - "At bond length of 2.3 Bohr, molecular hydrogen has:\n", - "Hartree-Fock energy of -0.730353319813 Hartree.\n", - "MP2 energy of -0.854203927291 Hartree.\n", - "FCI energy of -0.93892238579 Hartree.\n", - "Nuclear repulsion energy between protons is 0.230077047213 Hartree.\n", - "Spatial orbital 0 has energy of -0.233412208996 Hartree.\n", - "Spatial orbital 1 has energy of 0.069144997404 Hartree.\n", + "At bond length of 2.3 angstrom, molecular hydrogen has:\n", + "Hartree-Fock energy of -0.730353321355 Hartree.\n", + "MP2 energy of -0.854225123201 Hartree.\n", + "FCI energy of -0.938922385987 Hartree.\n", + "Nuclear repulsion energy between protons is 0.230077048226 Hartree.\n", + "Spatial orbital 0 has energy of -0.23341221005 Hartree.\n", + "Spatial orbital 1 has energy of 0.0691449985036 Hartree.\n", "2.4\n", "\n", - "At bond length of 2.4 Bohr, molecular hydrogen has:\n", + "At bond length of 2.4 angstrom, molecular hydrogen has:\n", "Hartree-Fock energy of -0.715910059008 Hartree.\n", "MP2 energy of -0.852827284386 Hartree.\n", "FCI energy of -0.937254952861 Hartree.\n", @@ -922,7 +922,7 @@ "Spatial orbital 1 has energy of 0.0589480319969 Hartree.\n", "2.5\n", "\n", - "At bond length of 2.5 Bohr, molecular hydrogen has:\n", + "At bond length of 2.5 angstrom, molecular hydrogen has:\n", "Hartree-Fock energy of -0.702943598373 Hartree.\n", "MP2 energy of -0.853517014087 Hartree.\n", "FCI energy of -0.936054919844 Hartree.\n", @@ -933,9 +933,9 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -966,7 +966,7 @@ " molecule.load()\n", "\n", " # Print out some results of calculation.\n", - " print('\\nAt bond length of {} Bohr, molecular hydrogen has:'.format(\n", + " print('\\nAt bond length of {} angstrom, molecular hydrogen has:'.format(\n", " bond_length))\n", " print('Hartree-Fock energy of {} Hartree.'.format(molecule.hf_energy))\n", " print('MP2 energy of {} Hartree.'.format(molecule.mp2_energy))\n", @@ -987,7 +987,7 @@ "plt.plot(bond_lengths, fci_energies, 'x-')\n", "plt.plot(bond_lengths, hf_energies, 'o-')\n", "plt.ylabel('Energy in Hartree')\n", - "plt.xlabel('Bond length in Bohr')\n", + "plt.xlabel('Bond length in angstrom')\n", "plt.show()" ] }, @@ -1009,7 +1009,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 14, "metadata": {}, "outputs": [ { @@ -1047,33 +1047,33 @@ "Ground state energy before rotation is -7.86277316303 Hartree.\n", "\n", "The Jordan-Wigner Hamiltonian in rotated basis follows:\n", - "-0.0458216167118 X1 Z2 X3 +\n", - "0.00343903835619 Z0 X1 Z2 X3 +\n", - "0.150166005227 Z2 +\n", - "0.00343903835619 X0 X2 +\n", - "0.00343903835619 Z0 Y1 Z2 Y3 +\n", - "0.0825436232346 Z0 Z1 +\n", - "-0.0204833860323 Y1 Y3 +\n", - "-0.0204833860323 X0 Z1 X2 Z3 +\n", - "-0.0106648548712 X0 X1 Y2 Y3 +\n", - "-0.00141494967741 Z0 +\n", + "-0.0875665154201 X1 Z2 X3 +\n", + "-0.0164315022477 Z0 X1 Z2 X3 +\n", + "0.0611119533053 Z2 +\n", + "-0.0164315022477 X0 X2 +\n", + "-0.0164315022477 Z0 Y1 Z2 Y3 +\n", + "0.0940698104517 Z0 Z1 +\n", + "-0.00335321084076 Y1 Y3 +\n", + "-0.00335321084076 X0 Z1 X2 Z3 +\n", + "-0.0237268902879 X0 X1 Y2 Y3 +\n", + "0.0876391022445 Z0 +\n", "0.0541304457933 Z1 Z3 +\n", - "-0.00141494967741 Z1 +\n", - "-0.0458216167118 X0 Z1 X2 +\n", - "-0.0204833860323 Y0 Z1 Y2 Z3 +\n", - "-0.0458216167118 Y0 Z1 Y2 +\n", + "0.0876391022445 Z1 +\n", + "-0.0875665154201 X0 Z1 X2 +\n", + "-0.00335321084076 Y0 Z1 Y2 Z3 +\n", + "-0.0875665154201 Y0 Z1 Y2 +\n", "0.0541304457933 Z0 Z2 +\n", - "0.111236456673 Z2 Z3 +\n", - "0.0106648548712 Y0 X1 X2 Y3 +\n", - "0.150166005227 Z3 +\n", - "0.0647953006645 Z1 Z2 +\n", - "-0.0458216167118 Y1 Z2 Y3 +\n", - "0.00343903835619 Y0 Y2 +\n", - "0.0647953006645 Z0 Z3 +\n", - "-0.0106648548712 Y0 Y1 X2 X3 +\n", + "0.0735861986226 Z2 Z3 +\n", + "0.0237268902879 Y0 X1 X2 Y3 +\n", + "0.0611119533053 Z3 +\n", + "0.0778573360812 Z1 Z2 +\n", + "-0.0875665154201 Y1 Z2 Y3 +\n", + "-0.0164315022477 Y0 Y2 +\n", + "0.0778573360812 Z0 Z3 +\n", + "-0.0237268902879 Y0 Y1 X2 X3 +\n", "-7.49894690201 I +\n", - "0.0106648548712 X0 Y1 Y2 X3 +\n", - "-0.0204833860323 X1 X3\n", + "0.0237268902879 X0 Y1 Y2 X3 +\n", + "-0.00335321084076 X1 X3\n", "Ground state energy after rotation is -7.86277316303 Hartree.\n" ] } @@ -1155,7 +1155,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 15, "metadata": { "collapsed": true }, @@ -1182,8 +1182,10 @@ }, { "cell_type": "code", - "execution_count": 8, - "metadata": {}, + "execution_count": 16, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "# Load the molecule.\n", @@ -1212,7 +1214,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 17, "metadata": { "collapsed": true }, @@ -1255,7 +1257,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 18, "metadata": {}, "outputs": [ { @@ -1269,7 +1271,7 @@ " Gradient evaluations: 3\n", "\n", "Optimal UCCSD Singlet Energy: -1.13727017463\n", - "Optimal UCCSD Singlet Amplitudes: [ -8.06258431e-09 5.65340577e-02]\n", + "Optimal UCCSD Singlet Amplitudes: [ -1.15179858e-08 5.65340587e-02]\n", "Classical CCSD Energy: -1.13727017465 Hartrees\n", "Exact FCI Energy: -1.13727017463 Hartrees\n", "Initial Energy of UCCSD with CCSD amplitudes: -1.13726981456 Hartrees\n" @@ -1302,7 +1304,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 19, "metadata": {}, "outputs": [ { @@ -1319,7 +1321,7 @@ "H | Qureg[3]\n", "CX | ( Qureg[1], Qureg[2] )\n", "CX | ( Qureg[2], Qureg[3] )\n", - "Rz(12.5663706063) | Qureg[3]\n", + "Rz(12.5663706028) | Qureg[3]\n", "CX | ( Qureg[2], Qureg[3] )\n", "CX | ( Qureg[1], Qureg[2] )\n", "H | Qureg[3]\n", @@ -1331,7 +1333,7 @@ "CX | ( Qureg[0], Qureg[1] )\n", "CX | ( Qureg[1], Qureg[2] )\n", "CX | ( Qureg[2], Qureg[3] )\n", - "Rz(12.5381035855) | Qureg[3]\n", + "Rz(12.538103585) | Qureg[3]\n", "CX | ( Qureg[2], Qureg[3] )\n", "CX | ( Qureg[1], Qureg[2] )\n", "CX | ( Qureg[0], Qureg[1] )\n", @@ -1346,7 +1348,7 @@ "CX | ( Qureg[0], Qureg[1] )\n", "CX | ( Qureg[1], Qureg[2] )\n", "CX | ( Qureg[2], Qureg[3] )\n", - "Rz(12.5381035855) | Qureg[3]\n", + "Rz(12.538103585) | Qureg[3]\n", "CX | ( Qureg[2], Qureg[3] )\n", "CX | ( Qureg[1], Qureg[2] )\n", "CX | ( Qureg[0], Qureg[1] )\n", @@ -1358,7 +1360,7 @@ "Rx(1.57079632679) | Qureg[3]\n", "CX | ( Qureg[1], Qureg[2] )\n", "CX | ( Qureg[2], Qureg[3] )\n", - "Rz(8.06258430583e-09) | Qureg[3]\n", + "Rz(1.15179857877e-08) | Qureg[3]\n", "CX | ( Qureg[2], Qureg[3] )\n", "CX | ( Qureg[1], Qureg[2] )\n", "Rx(10.9955742876) | Qureg[3]\n", @@ -1370,7 +1372,7 @@ "CX | ( Qureg[0], Qureg[1] )\n", "CX | ( Qureg[1], Qureg[2] )\n", "CX | ( Qureg[2], Qureg[3] )\n", - "Rz(0.0282670288423) | Qureg[3]\n", + "Rz(0.0282670293342) | Qureg[3]\n", "CX | ( Qureg[2], Qureg[3] )\n", "CX | ( Qureg[1], Qureg[2] )\n", "CX | ( Qureg[0], Qureg[1] )\n", @@ -1385,7 +1387,7 @@ "CX | ( Qureg[0], Qureg[1] )\n", "CX | ( Qureg[1], Qureg[2] )\n", "CX | ( Qureg[2], Qureg[3] )\n", - "Rz(12.5381035855) | Qureg[3]\n", + "Rz(12.538103585) | Qureg[3]\n", "CX | ( Qureg[2], Qureg[3] )\n", "CX | ( Qureg[1], Qureg[2] )\n", "CX | ( Qureg[0], Qureg[1] )\n", @@ -1400,7 +1402,7 @@ "CX | ( Qureg[0], Qureg[1] )\n", "CX | ( Qureg[1], Qureg[2] )\n", "CX | ( Qureg[2], Qureg[3] )\n", - "Rz(0.0282670288423) | Qureg[3]\n", + "Rz(0.0282670293342) | Qureg[3]\n", "CX | ( Qureg[2], Qureg[3] )\n", "CX | ( Qureg[1], Qureg[2] )\n", "CX | ( Qureg[0], Qureg[1] )\n", @@ -1412,7 +1414,7 @@ "H | Qureg[2]\n", "CX | ( Qureg[0], Qureg[1] )\n", "CX | ( Qureg[1], Qureg[2] )\n", - "Rz(12.5663706063) | Qureg[2]\n", + "Rz(12.5663706028) | Qureg[2]\n", "CX | ( Qureg[1], Qureg[2] )\n", "CX | ( Qureg[0], Qureg[1] )\n", "H | Qureg[2]\n", @@ -1421,7 +1423,7 @@ "Rx(1.57079632679) | Qureg[2]\n", "CX | ( Qureg[0], Qureg[1] )\n", "CX | ( Qureg[1], Qureg[2] )\n", - "Rz(8.06258430583e-09) | Qureg[2]\n", + "Rz(1.15179857877e-08) | Qureg[2]\n", "CX | ( Qureg[1], Qureg[2] )\n", "CX | ( Qureg[0], Qureg[1] )\n", "Rx(10.9955742876) | Qureg[2]\n", @@ -1433,7 +1435,7 @@ "CX | ( Qureg[0], Qureg[1] )\n", "CX | ( Qureg[1], Qureg[2] )\n", "CX | ( Qureg[2], Qureg[3] )\n", - "Rz(12.5381035855) | Qureg[3]\n", + "Rz(12.538103585) | Qureg[3]\n", "CX | ( Qureg[2], Qureg[3] )\n", "CX | ( Qureg[1], Qureg[2] )\n", "CX | ( Qureg[0], Qureg[1] )\n", @@ -1448,7 +1450,7 @@ "CX | ( Qureg[0], Qureg[1] )\n", "CX | ( Qureg[1], Qureg[2] )\n", "CX | ( Qureg[2], Qureg[3] )\n", - "Rz(0.0282670288423) | Qureg[3]\n", + "Rz(0.0282670293342) | Qureg[3]\n", "CX | ( Qureg[2], Qureg[3] )\n", "CX | ( Qureg[1], Qureg[2] )\n", "CX | ( Qureg[0], Qureg[1] )\n", @@ -1463,7 +1465,7 @@ "CX | ( Qureg[0], Qureg[1] )\n", "CX | ( Qureg[1], Qureg[2] )\n", "CX | ( Qureg[2], Qureg[3] )\n", - "Rz(0.0282670288423) | Qureg[3]\n", + "Rz(0.0282670293342) | Qureg[3]\n", "CX | ( Qureg[2], Qureg[3] )\n", "CX | ( Qureg[1], Qureg[2] )\n", "CX | ( Qureg[0], Qureg[1] )\n", diff --git a/setup.py b/setup.py index 07fcd74..e5f8b33 100755 --- a/setup.py +++ b/setup.py @@ -26,10 +26,8 @@ setup( name='fermilib', version=__version__, - author='Ryan Babbush, Jarrod McClean, Damian Steiger, Ian Kivlichan, ' - 'Thomas Haener, Vojtech Havlicek, Matthew Neeley, Wei Sun', - author_email='ryanbabbush@gmail.com, jarrod.mcc@gmail.com, ' - 'fermilib@projectq.ch', + author='FermiLib developers', + author_email='fermilib@projectq.ch', url='http://www.projectq.ch', description=('FermiLib - ' 'An open source package for analyzing, compiling and ' diff --git a/src/fermilib/_version.py b/src/fermilib/_version.py index eae1f87..61d3bbe 100644 --- a/src/fermilib/_version.py +++ b/src/fermilib/_version.py @@ -11,4 +11,4 @@ # limitations under the License. """Define version number here and read it from setup.py automatically""" -__version__ = "0.1a2" +__version__ = "0.1a3" diff --git a/src/fermilib/data/H1-Li1_sto-3g_singlet_1.45.hdf5 b/src/fermilib/data/H1-Li1_sto-3g_singlet_1.45.hdf5 index 003836c..c83fd3b 100644 Binary files a/src/fermilib/data/H1-Li1_sto-3g_singlet_1.45.hdf5 and b/src/fermilib/data/H1-Li1_sto-3g_singlet_1.45.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_0.1.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_0.1.hdf5 index 29c8d27..d986616 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_0.1.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_0.1.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_0.2.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_0.2.hdf5 index 40d6cdf..2d2ce75 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_0.2.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_0.2.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_0.3.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_0.3.hdf5 index 4290a22..060116d 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_0.3.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_0.3.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_0.4.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_0.4.hdf5 index a8e3af6..9f8293c 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_0.4.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_0.4.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_0.5.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_0.5.hdf5 index 4bba283..74178b2 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_0.5.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_0.5.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_0.6.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_0.6.hdf5 index 8199091..c823f0e 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_0.6.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_0.6.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_0.7.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_0.7.hdf5 index 0e68f61..ffd5745 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_0.7.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_0.7.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_0.7414.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_0.7414.hdf5 index 9db7414..38b456f 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_0.7414.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_0.7414.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_0.8.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_0.8.hdf5 index 74dead8..28da847 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_0.8.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_0.8.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_0.9.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_0.9.hdf5 index 858e8ee..08bcff3 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_0.9.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_0.9.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_1.0.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_1.0.hdf5 index a22c99b..af9ec9f 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_1.0.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_1.0.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_1.1.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_1.1.hdf5 index 8a40567..c7a63e4 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_1.1.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_1.1.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_1.2.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_1.2.hdf5 index a476580..ddab4e3 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_1.2.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_1.2.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_1.3.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_1.3.hdf5 index 1902aa8..8b11371 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_1.3.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_1.3.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_1.4.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_1.4.hdf5 index 5a4837e..bdf419a 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_1.4.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_1.4.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_1.5.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_1.5.hdf5 index b3ba604..aee5c0f 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_1.5.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_1.5.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_1.6.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_1.6.hdf5 index b5f1250..9977640 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_1.6.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_1.6.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_1.7.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_1.7.hdf5 index e3ddf26..1696be7 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_1.7.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_1.7.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_1.8.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_1.8.hdf5 index c94e748..1ae74b2 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_1.8.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_1.8.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_1.9.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_1.9.hdf5 index af88ba1..a7e341e 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_1.9.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_1.9.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_2.0.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_2.0.hdf5 index 7c261d4..4106c0b 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_2.0.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_2.0.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_2.1.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_2.1.hdf5 index ef6ebc3..c9e5efd 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_2.1.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_2.1.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_2.2.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_2.2.hdf5 index 90cbe61..6ff38b9 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_2.2.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_2.2.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_2.3.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_2.3.hdf5 index aefa672..a90c88b 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_2.3.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_2.3.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_2.4.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_2.4.hdf5 index 939c358..3f5e39a 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_2.4.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_2.4.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_2.5.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_2.5.hdf5 index 7b5e972..231953e 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_2.5.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_2.5.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_2.6.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_2.6.hdf5 index 99b209b..a241cfe 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_2.6.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_2.6.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_2.7.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_2.7.hdf5 index 7e08745..d1d513b 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_2.7.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_2.7.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_2.8.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_2.8.hdf5 index 085611b..5be2594 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_2.8.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_2.8.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_2.9.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_2.9.hdf5 index f9a71e9..f491c05 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_2.9.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_2.9.hdf5 differ diff --git a/src/fermilib/data/H2_sto-3g_singlet_3.0.hdf5 b/src/fermilib/data/H2_sto-3g_singlet_3.0.hdf5 index 1209525..465d732 100644 Binary files a/src/fermilib/data/H2_sto-3g_singlet_3.0.hdf5 and b/src/fermilib/data/H2_sto-3g_singlet_3.0.hdf5 differ diff --git a/src/fermilib/ops/_fermion_operator.py b/src/fermilib/ops/_fermion_operator.py index 4e1dca7..a4d1287 100644 --- a/src/fermilib/ops/_fermion_operator.py +++ b/src/fermilib/ops/_fermion_operator.py @@ -514,7 +514,7 @@ def __iadd__(self, addend): """In-place method for += addition of FermionOperator. Args: - addend: A FermionOperator. + addend (FermionOperator): The operator to add. Returns: sum (FermionOperator): Mutated self. @@ -548,16 +548,43 @@ def __add__(self, addend): summand += addend return summand + def __isub__(self, subtrahend): + """In-place method for -= subtraction of FermionOperator. + + Args: + subtrahend (A FermionOperator): The operator to subtract. + + Returns: + difference (FermionOperator): Mutated self. + + Raises: + TypeError: Cannot subtract invalid type. + """ + if isinstance(subtrahend, FermionOperator): + for term in subtrahend.terms: + if term in self.terms: + if abs(self.terms[term] - + subtrahend.terms[term]) < EQ_TOLERANCE: + del self.terms[term] + else: + self.terms[term] -= subtrahend.terms[term] + else: + self.terms[term] = -subtrahend.terms[term] + else: + raise TypeError('Cannot subtract invalid type.') + return self + def __sub__(self, subtrahend): """ Args: subtrahend (FermionOperator): The operator to subtract. + Returns: difference (FermionOperator) """ - if not isinstance(subtrahend, FermionOperator): - raise TypeError('Cannot subtract invalid type to FermionOperator.') - return self + (-1. * subtrahend) + minuend = copy.deepcopy(self) + minuend -= subtrahend + return minuend def __neg__(self): """ diff --git a/src/fermilib/ops/_fermion_operator_test.py b/src/fermilib/ops/_fermion_operator_test.py index 0276185..3411440 100644 --- a/src/fermilib/ops/_fermion_operator_test.py +++ b/src/fermilib/ops/_fermion_operator_test.py @@ -146,6 +146,10 @@ def test_init_bad_mode_num(self): with self.assertRaises(FermionOperatorError): _ = FermionOperator('-1^') + def test_init_invalid_tensor_factor(self): + with self.assertRaises(FermionOperatorError): + _ = FermionOperator(((-2, 1), (1, 0))) + def test_FermionOperator(self): op = FermionOperator((), 3.) self.assertTrue(op.isclose(FermionOperator(()) * 3.)) @@ -621,6 +625,15 @@ def test_hermitian_conjugated_semihermitian(self): FermionOperator('2^ 2', -0.1j)) self.assertTrue(op_hc.isclose(hermitian_conjugated(op))) + def test_compress_terms(self): + op = (FermionOperator('3^ 1', 0.3 + 3e-11j) + + FermionOperator('2^ 3', 5e-10) + + FermionOperator('1^ 3', 1e-3)) + op_compressed = (FermionOperator('3^ 1', 0.3) + + FermionOperator('1^ 3', 1e-3)) + op.compress(1e-7) + self.assertTrue(op_compressed.isclose(op)) + def test_is_normal_ordered_empty(self): op = FermionOperator() * 2 self.assertTrue(op.is_normal_ordered()) @@ -743,12 +756,10 @@ def test_str(self): self.assertEqual(str(op), "0.5 [1^ 3 8^]") op2 = FermionOperator((), 2) self.assertEqual(str(op2), "2 []") + op3 = FermionOperator() + self.assertEqual(str(op3), "0") def test_rep(self): op = FermionOperator(((1, 1), (3, 0), (8, 1)), 0.5) # Not necessary, repr could do something in addition self.assertEqual(repr(op), str(op)) - - -if __name__ == '__main__': - unittest.main() diff --git a/src/fermilib/ops/_interaction_operator_test.py b/src/fermilib/ops/_interaction_operator_test.py index aded946..b6e5f0a 100644 --- a/src/fermilib/ops/_interaction_operator_test.py +++ b/src/fermilib/ops/_interaction_operator_test.py @@ -76,8 +76,3 @@ def test_eight_point_iter(self): for key in interaction_operator.unique_iter(): got_str += '{}\n'.format(interaction_operator[key]) self.assertEqual(want_str, got_str) - - -# Test. -if __name__ == '__main__': - unittest.main() diff --git a/src/fermilib/ops/_interaction_rdm_test.py b/src/fermilib/ops/_interaction_rdm_test.py index 0fe3c6d..4b17e2a 100644 --- a/src/fermilib/ops/_interaction_rdm_test.py +++ b/src/fermilib/ops/_interaction_rdm_test.py @@ -16,8 +16,11 @@ import unittest from fermilib.config import * -from fermilib.utils import MolecularData +from fermilib.ops._interaction_rdm import InteractionRDMError from fermilib.transforms import jordan_wigner +from fermilib.utils import MolecularData + +from projectq.ops import QubitOperator class InteractionRDMTest(unittest.TestCase): @@ -46,11 +49,20 @@ def test_get_qubit_expectations(self): qubit_expectations.terms[qubit_term]) self.assertLess(abs(test_energy - self.cisd_energy), EQ_TOLERANCE) + def test_get_qubit_expectations_nonmolecular_term(self): + with self.assertRaises(InteractionRDMError): + self.rdm.get_qubit_expectations(QubitOperator('X1 X2 X3 X4 Y6')) + + def test_get_qubit_expectations_through_expectation_method(self): + qubit_operator = jordan_wigner(self.hamiltonian) + test_energy = self.rdm.expectation(qubit_operator) + + self.assertLess(abs(test_energy - self.cisd_energy), EQ_TOLERANCE) + def test_get_molecular_operator_expectation(self): expectation = self.rdm.expectation(self.hamiltonian) self.assertAlmostEqual(expectation, self.cisd_energy, places=7) - -# Test. -if __name__ == '__main__': - unittest.main() + def test_expectation_bad_type(self): + with self.assertRaises(InteractionRDMError): + self.rdm.expectation(12) diff --git a/src/fermilib/ops/_interaction_tensor.py b/src/fermilib/ops/_interaction_tensor.py index 1d7b596..4945dcc 100644 --- a/src/fermilib/ops/_interaction_tensor.py +++ b/src/fermilib/ops/_interaction_tensor.py @@ -144,10 +144,7 @@ def __getitem__(self, args): p, q = args return self.one_body_tensor[p, q] elif not len(args): - if self.constant is None: - raise ValueError('args must be of length 2, or 4.') - else: - return self.constant + return self.constant else: raise ValueError('args must be of length 0, 2, or 4.') diff --git a/src/fermilib/ops/_interaction_tensor_test.py b/src/fermilib/ops/_interaction_tensor_test.py index 86d5f0a..2908640 100644 --- a/src/fermilib/ops/_interaction_tensor_test.py +++ b/src/fermilib/ops/_interaction_tensor_test.py @@ -82,6 +82,59 @@ def setUp(self): one_body_axb, two_body_axb) + self.n_qubits_plus_one = self.n_qubits + 1 + one_body_c = numpy.zeros((self.n_qubits_plus_one, + self.n_qubits_plus_one)) + two_body_c = numpy.zeros((self.n_qubits_plus_one, + self.n_qubits_plus_one, + self.n_qubits_plus_one, + self.n_qubits_plus_one)) + one_body_c[0, 1] = 1 + one_body_c[1, 0] = 2 + two_body_c[0, 1, 0, 1] = 3 + two_body_c[1, 0, 0, 1] = 4 + self.interaction_tensor_c = InteractionTensor(self.constant, + one_body_c, + two_body_c) + + def test_setitem_1body(self): + expected_one_body_tensor = numpy.array([[0, 3], [2, 0]]) + self.interaction_tensor_a[0, 1] = 3 + self.interaction_tensor_a[1, 0] = 2 + self.assertTrue(numpy.allclose( + self.interaction_tensor_a.one_body_tensor, + expected_one_body_tensor)) + + def test_getitem_1body(self): + self.assertEqual(self.interaction_tensor_c[0, 1], 1) + self.assertEqual(self.interaction_tensor_c[1, 0], 2) + + def test_setitem_2body(self): + self.interaction_tensor_a[0, 1, 1, 0] = 3 + self.interaction_tensor_a[1, 0, 0, 1] = 2 + self.assertEqual(self.interaction_tensor_a.two_body_tensor[0, 1, 1, 0], + 3) + self.assertEqual(self.interaction_tensor_a.two_body_tensor[1, 0, 0, 1], + 2) + + def test_getitem_2body(self): + self.assertEqual(self.interaction_tensor_c[0, 1, 0, 1], 3) + self.assertEqual(self.interaction_tensor_c[1, 0, 0, 1], 4) + + def test_invalid_getitem_indexing(self): + with self.assertRaises(ValueError): + self.interaction_tensor_a[0, 0, 0] + + def test_invalid_setitem_indexing(self): + test_tensor = copy.deepcopy(self.interaction_tensor_a) + with self.assertRaises(ValueError): + test_tensor[0, 0, 0] = 5 + + def test_neq(self): + self.assertNotEqual(self.interaction_tensor_a, + self.interaction_tensor_b) + self.assertTrue(self.interaction_tensor_a != self.interaction_tensor_b) + def test_add(self): new_tensor = self.interaction_tensor_a + self.interaction_tensor_b self.assertEqual(new_tensor, self.interaction_tensor_ab) @@ -91,6 +144,14 @@ def test_iadd(self): new_tensor += self.interaction_tensor_b self.assertEqual(new_tensor, self.interaction_tensor_ab) + def test_invalid_addend(self): + with self.assertRaises(TypeError): + self.interaction_tensor_a + 2 + + def test_invalid_tensor_shape_add(self): + with self.assertRaises(TypeError): + self.interaction_tensor_a + self.interaction_tensor_c + def test_neg(self): self.assertEqual(-self.interaction_tensor_a, self.interaction_tensor_na) @@ -104,6 +165,14 @@ def test_isub(self): new_tensor -= self.interaction_tensor_b self.assertEqual(new_tensor, self.interaction_tensor_a) + def test_invalid_subtrahend(self): + with self.assertRaises(TypeError): + self.interaction_tensor_a - 2 + + def test_invalid_tensor_shape_sub(self): + with self.assertRaises(TypeError): + self.interaction_tensor_a - self.interaction_tensor_c + def test_mul(self): new_tensor = self.interaction_tensor_a * self.interaction_tensor_b self.assertEqual(new_tensor, self.interaction_tensor_axb) @@ -113,6 +182,14 @@ def test_imul(self): new_tensor *= self.interaction_tensor_b self.assertEqual(new_tensor, self.interaction_tensor_axb) + def test_invalid_multiplier(self): + with self.assertRaises(TypeError): + self.interaction_tensor_a * 2 + + def test_invalid_tensor_shape_mult(self): + with self.assertRaises(TypeError): + self.interaction_tensor_a * self.interaction_tensor_c + def test_iter_and_str(self): one_body = numpy.zeros((self.n_qubits, self.n_qubits)) two_body = numpy.zeros((self.n_qubits, self.n_qubits, @@ -123,6 +200,7 @@ def test_iter_and_str(self): one_body, two_body) want_str = '[] 23.0\n[0 1] 11.0\n[0 1 1 0] 22.0\n' self.assertEqual(interaction_tensor.__str__(), want_str) + self.assertEqual(interaction_tensor.__repr__(), want_str) def test_rotate_basis_identical(self): rotation_matrix_identical = numpy.zeros((self.n_qubits, self.n_qubits)) @@ -184,8 +262,3 @@ def test_rotate_basis_reverse(self): two_body_reverse) interaction_tensor.rotate_basis(rotation_matrix_reverse) self.assertEqual(interaction_tensor, want_interaction_tensor) - - -# Test. -if __name__ == '__main__': - unittest.main() diff --git a/src/fermilib/tests/_hydrogen_integration_test.py b/src/fermilib/tests/_hydrogen_integration_test.py index 5397074..8d2fe9a 100644 --- a/src/fermilib/tests/_hydrogen_integration_test.py +++ b/src/fermilib/tests/_hydrogen_integration_test.py @@ -232,8 +232,8 @@ def test_ucc(self): # Test UCCSD for accuracy against FCI using loaded t amplitudes. ucc_operator = uccsd_operator( - self.molecule.ccsd_amplitudes.one_body_tensor, - self.molecule.ccsd_amplitudes.two_body_tensor) + self.molecule.ccsd_single_amps, + self.molecule.ccsd_double_amps) hf_state = jw_hartree_fock_state( self.molecule.n_electrons, count_qubits(self.qubit_hamiltonian)) @@ -248,8 +248,8 @@ def test_ucc(self): # Test CCSD for precise match against FCI using loaded t amplitudes. ccsd_operator = uccsd_operator( - self.molecule.ccsd_amplitudes.one_body_tensor, - self.molecule.ccsd_amplitudes.two_body_tensor, + self.molecule.ccsd_single_amps, + self.molecule.ccsd_double_amps, anti_hermitian=False) ccsd_sparse_r = jordan_wigner_sparse(ccsd_operator) @@ -258,8 +258,8 @@ def test_ucc(self): # Test CCSD for precise match against FCI using loaded t amplitudes ccsd_operator = uccsd_operator( - self.molecule.ccsd_amplitudes.one_body_tensor, - self.molecule.ccsd_amplitudes.two_body_tensor, + self.molecule.ccsd_single_amps, + self.molecule.ccsd_double_amps, anti_hermitian=False) ccsd_sparse_r = jordan_wigner_sparse(ccsd_operator) @@ -272,7 +272,3 @@ def test_ucc(self): expected_ccsd_energy = ccsd_state_l.getH().dot( self.hamiltonian_matrix.dot(ccsd_state_r))[0, 0] self.assertAlmostEqual(expected_ccsd_energy, self.molecule.fci_energy) - - -if __name__ == '__main__': - unittest.main() diff --git a/src/fermilib/tests/_lih_integration_test.py b/src/fermilib/tests/_lih_integration_test.py index 1a29c6e..61842a0 100644 --- a/src/fermilib/tests/_lih_integration_test.py +++ b/src/fermilib/tests/_lih_integration_test.py @@ -129,7 +129,3 @@ def test_all(self): self.hamiltonian_matrix_no_core.todense())[0][0] self.assertAlmostEqual(no_core_fci_energy, self.frozen_core_fci_energy) - - -if __name__ == '__main__': - unittest.main() diff --git a/src/fermilib/transforms/_bravyi_kitaev_test.py b/src/fermilib/transforms/_bravyi_kitaev_test.py index 99fd2b7..8a0a34c 100644 --- a/src/fermilib/transforms/_bravyi_kitaev_test.py +++ b/src/fermilib/transforms/_bravyi_kitaev_test.py @@ -67,6 +67,10 @@ def test_bk_identity(self): self.assertTrue(bravyi_kitaev(FermionOperator(())).isclose( QubitOperator(()))) + def test_bk_n_qubits_too_small(self): + with self.assertRaises(ValueError): + bravyi_kitaev(FermionOperator('2^ 3^ 5 0'), n_qubits=4) + def test_bk_jw_number_operator(self): # Check if number operator has the same spectrum in both # BK and JW representations @@ -191,7 +195,3 @@ def test_bk_jw_integration_original(self): bk_spectrum = eigenspectrum(bk_qubit_operator) self.assertAlmostEqual(0., numpy.amax(numpy.absolute(jw_spectrum - bk_spectrum)), places=5) - - -if __name__ == '__main__': - unittest.main() diff --git a/src/fermilib/transforms/_conversion.py b/src/fermilib/transforms/_conversion.py index 8b33aa6..bb1d0b4 100644 --- a/src/fermilib/transforms/_conversion.py +++ b/src/fermilib/transforms/_conversion.py @@ -103,7 +103,7 @@ def get_interaction_operator(fermion_operator, n_qubits=None): if n_qubits is None: n_qubits = count_qubits(fermion_operator) if n_qubits < count_qubits(fermion_operator): - n_qubits = count_qubits(fermion_operator) + raise ValueError('Invalid number of qubits specified.') # Normal order the terms and initialize. fermion_operator = normal_ordered(fermion_operator) diff --git a/src/fermilib/transforms/_conversion_test.py b/src/fermilib/transforms/_conversion_test.py index fc09780..e3c7494 100644 --- a/src/fermilib/transforms/_conversion_test.py +++ b/src/fermilib/transforms/_conversion_test.py @@ -17,10 +17,11 @@ import numpy import unittest -from fermilib.ops import (InteractionOperator, - FermionOperator, +from fermilib.ops import (FermionOperator, + InteractionOperator, normal_ordered, number_operator) +from fermilib.ops._interaction_operator import InteractionOperatorError from fermilib.transforms import * from fermilib.utils import * @@ -39,6 +40,26 @@ def test_get_molecular_operator(self): fermion_operator = normal_ordered(fermion_operator) self.assertTrue(normal_ordered(op).isclose(fermion_operator)) + def test_get_interaction_operator_bad_input(self): + with self.assertRaises(TypeError): + get_interaction_operator('3') + + def test_get_interaction_operator_too_few_qubits(self): + with self.assertRaises(ValueError): + get_interaction_operator(FermionOperator('3^ 2^ 1 0'), 3) + + def test_get_interaction_operator_bad_1body_term(self): + with self.assertRaises(InteractionOperatorError): + get_interaction_operator(FermionOperator('1^ 0^')) + + def test_get_interaction_operator_bad_2body_term(self): + with self.assertRaises(InteractionOperatorError): + get_interaction_operator(FermionOperator('3^ 2 1 0')) + + def test_get_interaction_operator_nonmolecular_term(self): + with self.assertRaises(InteractionOperatorError): + get_interaction_operator(FermionOperator('3^ 2 1')) + class GetSparseOperatorQubitTest(unittest.TestCase): @@ -122,7 +143,3 @@ def test_sparse_matrix_zero_n_qubit(self): sparse_operator.eliminate_zeros() self.assertEqual(len(list(sparse_operator.data)), 0) self.assertEqual(sparse_operator.shape, (16, 16)) - - -if __name__ == '__main__': - unittest.main() diff --git a/src/fermilib/transforms/_jordan_wigner.py b/src/fermilib/transforms/_jordan_wigner.py index e8edb69..7ef23cf 100644 --- a/src/fermilib/transforms/_jordan_wigner.py +++ b/src/fermilib/transforms/_jordan_wigner.py @@ -72,7 +72,7 @@ def jordan_wigner_interaction_op(iop, n_qubits=None): if n_qubits is None: n_qubits = count_qubits(iop) if n_qubits < count_qubits(iop): - n_qubits = count_qubits(iop) + raise ValueError('Invalid number of qubits specified.') # Initialize qubit operator as constant. qubit_operator = QubitOperator((), iop.constant) diff --git a/src/fermilib/transforms/_jordan_wigner_test.py b/src/fermilib/transforms/_jordan_wigner_test.py index 746f1c5..22f439c 100644 --- a/src/fermilib/transforms/_jordan_wigner_test.py +++ b/src/fermilib/transforms/_jordan_wigner_test.py @@ -25,7 +25,8 @@ reverse_jordan_wigner) from fermilib.transforms._jordan_wigner import (jordan_wigner, jordan_wigner_one_body, - jordan_wigner_two_body) + jordan_wigner_two_body, + jordan_wigner_interaction_op) from projectq.ops import QubitOperator @@ -271,6 +272,21 @@ def test_jordan_wigner_twobody_interaction_op_reversal_symmetric(self): self.assertTrue(jordan_wigner(test_op).isclose( jordan_wigner(get_interaction_operator(test_op)))) + def test_jordan_wigner_interaction_op_too_few_n_qubits(self): + with self.assertRaises(ValueError): + jordan_wigner_interaction_op(self.interaction_operator, + self.n_qubits - 2) + + def test_jordan_wigner_interaction_op_with_zero_term(self): + test_op = FermionOperator('1^ 2^ 3 4') + test_op += hermitian_conjugated(test_op) + + interaction_op = get_interaction_operator(test_op) + interaction_op.constant = 0.0 + + retransformed_test_op = reverse_jordan_wigner(jordan_wigner( + interaction_op)) + class GetInteractionOperatorTest(unittest.TestCase): @@ -325,7 +341,3 @@ def test_get_interaction_operator_two_body_distinct(self): two_body[1, 0, 3, 2] = 1. self.assertEqual(interaction_operator, InteractionOperator(0.0, self.one_body, two_body)) - - -if __name__ == '__main__': - unittest.main() diff --git a/src/fermilib/transforms/_reverse_jordan_wigner.py b/src/fermilib/transforms/_reverse_jordan_wigner.py index 0fe83d8..f34ad06 100644 --- a/src/fermilib/transforms/_reverse_jordan_wigner.py +++ b/src/fermilib/transforms/_reverse_jordan_wigner.py @@ -49,8 +49,7 @@ def reverse_jordan_wigner(qubit_operator, n_qubits=None): if n_qubits is None: n_qubits = count_qubits(qubit_operator) if n_qubits < count_qubits(qubit_operator): - raise TypeError( - 'Invalid number of qubits specified') + raise ValueError('Invalid number of qubits specified.') # Loop through terms. transformed_operator = FermionOperator() @@ -73,9 +72,7 @@ def reverse_jordan_wigner(qubit_operator, n_qubits=None): if pauli_operator[1] == 'Y': raising_term *= 1.j lowering_term *= -1.j - elif pauli_operator[1] != 'X': - raise TypeError( - 'Pauli operators must be X, Y, or Z') + transformed_pauli = raising_term + lowering_term # Account for the phase terms. diff --git a/src/fermilib/transforms/_reverse_jordan_wigner_test.py b/src/fermilib/transforms/_reverse_jordan_wigner_test.py index d3c7b11..6dc2b7f 100644 --- a/src/fermilib/transforms/_reverse_jordan_wigner_test.py +++ b/src/fermilib/transforms/_reverse_jordan_wigner_test.py @@ -67,6 +67,10 @@ def test_z(self): retransmed_z = jordan_wigner(transmed_z) self.assertTrue(pauli_z.isclose(retransmed_z)) + def test_reverse_jw_too_few_n_qubits(self): + with self.assertRaises(ValueError): + reverse_jordan_wigner(self.operator_a, 0) + def test_identity(self): n_qubits = 5 transmed_i = reverse_jordan_wigner(self.identity, n_qubits) @@ -174,6 +178,3 @@ def test_reverse_jw_linearity(self): def test_bad_type(self): with self.assertRaises(TypeError): reverse_jordan_wigner(3) - -if __name__ == '__main__': - unittest.main() diff --git a/src/fermilib/utils/__init__.py b/src/fermilib/utils/__init__.py index e9ebcc0..287f67f 100644 --- a/src/fermilib/utils/__init__.py +++ b/src/fermilib/utils/__init__.py @@ -19,22 +19,30 @@ from ._hubbard import fermi_hubbard -from ._jellium import (jellium_model, - jordan_wigner_position_jellium, - momentum_kinetic_operator, - momentum_potential_operator, - position_kinetic_operator, - position_potential_operator) +from ._jellium import (dual_basis_kinetic, + dual_basis_potential, + dual_basis_jellium_model, + jellium_model, + jordan_wigner_dual_basis_jellium, + plane_wave_kinetic, + plane_wave_potential) + +from ._dual_basis_trotter_error import (dual_basis_error_bound, + dual_basis_error_operator) from ._molecular_data import MolecularData, periodic_table -from ._operator_utils import (eigenspectrum, commutator, - count_qubits, is_identity) +from ._operator_utils import (commutator, count_qubits, + eigenspectrum, get_file_path, is_identity, + load_operator, save_operator) -from ._plane_wave_hamiltonian import (inverse_fourier_transform, +from ._plane_wave_hamiltonian import (dual_basis_external_potential, fourier_transform, + inverse_fourier_transform, + plane_wave_external_potential, plane_wave_hamiltonian, - jordan_wigner_dual_basis_hamiltonian) + jordan_wigner_dual_basis_hamiltonian, + wigner_seitz_length_scale) from ._sparse_tools import (expectation, get_density_matrix, diff --git a/src/fermilib/utils/_chemical_series_test.py b/src/fermilib/utils/_chemical_series_test.py index cc0c9c4..73aa084 100644 --- a/src/fermilib/utils/_chemical_series_test.py +++ b/src/fermilib/utils/_chemical_series_test.py @@ -21,7 +21,7 @@ make_atomic_lattice, make_atomic_ring, periodic_table) - +from fermilib.utils._chemical_series import MolecularLatticeError from fermilib.utils._molecular_data import periodic_polarization @@ -116,6 +116,15 @@ def test_make_atomic_lattice_3d(self): grid_x = atom // atom_dim ** 2 self.assertAlmostEqual(coords[0], spacing * grid_x) + def test_make_atomic_lattice_0d_raise_error(self): + spacing = 1.7 + basis = 'sto-3g' + atom_type = 'H' + atom_dim = 0 + with self.assertRaises(MolecularLatticeError): + make_atomic_lattice(atom_dim, atom_dim, atom_dim, + spacing, basis, atom_type) + def test_make_atom(self): basis = 'sto-3g' largest_atom = 30 @@ -125,7 +134,3 @@ def test_make_atom(self): expected_spin = periodic_polarization[n_electrons] / 2. expected_multiplicity = int(2 * expected_spin + 1) self.assertAlmostEqual(expected_multiplicity, atom.multiplicity) - - -if __name__ == '__main__': - unittest.main() diff --git a/src/fermilib/utils/_dual_basis_trotter_error.py b/src/fermilib/utils/_dual_basis_trotter_error.py new file mode 100644 index 0000000..18d5b7f --- /dev/null +++ b/src/fermilib/utils/_dual_basis_trotter_error.py @@ -0,0 +1,520 @@ +# 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 compute Trotter errors in the plane-wave dual basis.""" +from __future__ import absolute_import +from future.utils import iteritems, itervalues + +import numpy + +from fermilib.config import * +from fermilib.ops import FermionOperator, normal_ordered +from fermilib.utils import Grid, jellium_model +from fermilib.utils._operator_utils import commutator, count_qubits +from fermilib.utils._plane_wave_hamiltonian import wigner_seitz_length_scale + + +def double_commutator(op1, op2, op3, indices2=None, indices3=None, + is_hopping_operator2=None, is_hopping_operator3=None): + """Return the double commutator [op1, [op2, op3]]. + + Assumes the operators are from the dual basis Hamiltonian. + + Args: + op1, op2, op3 (FermionOperators): operators for the commutator. + indices2, indices3 (set): The indices op2 and op3 act on. + is_hopping_operator2 (bool): Whether op2 is a hopping operator. + is_hopping_operator3 (bool): Whether op3 is a hopping operator. + + Returns: + The double commutator of the given operators. + """ + if is_hopping_operator2 and is_hopping_operator3: + indices2 = set(indices2) + indices3 = set(indices3) + # Determine which indices both op2 and op3 act on. + try: + intersection, = indices2.intersection(indices3) + except ValueError: + return FermionOperator.zero() + + # Remove the intersection from the set of indices, since it will get + # cancelled out in the final result. + indices2.remove(intersection) + indices3.remove(intersection) + + # Find the indices of the final output hopping operator. + index2, = indices2 + index3, = indices3 + coeff2 = op2.terms[list(op2.terms)[0]] + coeff3 = op3.terms[list(op3.terms)[0]] + commutator23 = ( + FermionOperator(((index2, 1), (index3, 0)), coeff2 * coeff3) + + FermionOperator(((index3, 1), (index2, 0)), -coeff2 * coeff3)) + else: + commutator23 = normal_ordered(commutator(op2, op3)) + + return normal_ordered(commutator(op1, commutator23)) + + +def trivially_double_commutes_dual_basis_using_term_info( + indices_alpha=None, indices_beta=None, indices_alpha_prime=None, + is_hopping_operator_alpha=None, is_hopping_operator_beta=None, + is_hopping_operator_alpha_prime=None, jellium_only=False): + """Return whether [op_a, [op_b, op_a_prime]] is trivially zero. + + Assumes all the operators are FermionOperators from the dual basis + Hamiltonian, broken into the form i^j^ i j + c_i*(i^ i) + c_j*(j^ j) + or i^ j + j^ i, where i and j are modes and c is a constant. For the + full dual basis Hamiltonian, i^ i and j^ j can have distinct + coefficients c_i and c_j: for jellium they are necessarily the same. + If this is the case, jellium_only should be set to True. + + The operators are determined by the indices they act on and by + whether they are hopping operators (i^ j + j^ i) or number operators + (i^ j^ i j + c_i*(i^ i) + c_j*(j^ j)). a, b, and a_prime are + shorthands for alpha, beta, and alpha_prime. + + Args: + indices_alpha (set): The indices term_alpha acts on. + indices_beta (set): The indices term_beta acts on. + indices_alpha_prime (set): The indices term_alpha_prime acts on. + is_hopping_operator_alpha (bool): Whether term_alpha is a + hopping operator. + is_hopping_operator_beta (bool): Whether term_beta is a + hopping operator. + is_hopping_operator_alpha_prime (bool): Whether term_alpha_prime + is a hopping operator. + jellium_only (bool): Whether the terms are only from the jellium + Hamiltonian, i.e. if c_i = c for all number + operators i^ i or if it depends on i. + + Returns: + Whether or not the double commutator is trivially zero. + """ + # If operator_beta and operator_alpha_prime (in the inner commutator) + # are number operators, they commute trivially. + if not (is_hopping_operator_beta or is_hopping_operator_alpha_prime): + return True + + # The operators in the jellium Hamiltonian (provided they are of the + # form i^ i + j^ j or i^ j^ i j + c*(i^ i + j^ j), and not both + # hopping operators) commute if they act on the same modes or if + # there is no intersection. + if (jellium_only and (not is_hopping_operator_alpha_prime or + not is_hopping_operator_beta) and + len(indices_beta.intersection(indices_alpha_prime)) != 1): + return True + + # If the modes operator_alpha acts on are disjoint with the modes + # operator_beta and operator_alpha_prime act on, they commute. + if not indices_alpha.intersection(indices_beta.union(indices_alpha_prime)): + return True + + return False + + +def trivially_commutes_dual_basis(term_a, term_b): + """Determine whether the given terms trivially commute. + + Assumes the terms are single-term FermionOperators from the + plane-wave dual basis Hamiltonian. + + Args: + term_a, term_b (FermionOperator): Single-term FermionOperators. + + Returns: + Whether or not the commutator is trivially zero. + """ + modes_acted_on_by_term_a, = term_a.terms.keys() + modes_acted_on_by_term_b, = term_b.terms.keys() + + modes_touched_a = [modes_acted_on_by_term_a[0][0], + modes_acted_on_by_term_a[1][0]] + modes_touched_b = [modes_acted_on_by_term_b[0][0], + modes_acted_on_by_term_b[1][0]] + + # If there's no intersection between the modes term_a and term_b act + # on, the commutator is zero. + if not (modes_touched_a[0] in modes_touched_b or + modes_touched_a[1] in modes_touched_b): + return True + + # In the dual basis, possible number operators take the form + # a^ a or a^ b^ a b. Number operators always commute trivially. + term_a_is_number_operator = ( + modes_acted_on_by_term_a[0][0] == modes_acted_on_by_term_a[1][0] or + modes_acted_on_by_term_a[1][1]) + term_b_is_number_operator = ( + modes_acted_on_by_term_b[0][0] == modes_acted_on_by_term_b[1][0] or + modes_acted_on_by_term_b[1][1]) + if term_a_is_number_operator and term_b_is_number_operator: + return True + + # If the first commutator's terms are both hopping, and both create + # or annihilate the same mode, then the result is zero. + if not (term_a_is_number_operator or term_b_is_number_operator): + if (modes_acted_on_by_term_a[0][0] == modes_acted_on_by_term_b[0][0] or + modes_acted_on_by_term_a[1][0] == + modes_acted_on_by_term_b[1][0]): + return True + + # If both terms act on the same operators and are not both hopping + # operators, then they commute. + if ((term_a_is_number_operator or term_b_is_number_operator) and + set(modes_touched_a) == set(modes_touched_b)): + return True + + return False + + +def trivially_double_commutes_dual_basis(term_a, term_b, term_c): + """Check if the double commutator [term_a, [term_b, term_c]] is zero. + + Assumes the terms are single-term FermionOperators from the + plane-wave dual basis Hamiltonian. + + Args: + term_a, term_b, term_c: Single-term FermionOperators. + + Notes: + This function inlines trivially_commutes_dual_basis for terms b + and c. + + Returns: + Whether or not the double commutator is trivially zero. + """ + # Determine the set of modes each term acts on. + modes_acted_on_by_term_b, = term_b.terms.keys() + modes_acted_on_by_term_c, = term_c.terms.keys() + + modes_touched_c = [modes_acted_on_by_term_c[0][0], + modes_acted_on_by_term_c[1][0]] + + # If there's no intersection between the modes term_b and term_c act + # on, the commutator is trivially zero. + if not (modes_acted_on_by_term_b[0][0] in modes_touched_c or + modes_acted_on_by_term_b[1][0] in modes_touched_c): + return True + + # In the dual_basis Hamiltonian, possible number operators take the + # form a^ a or a^ b^ a b. Check for this. + term_b_is_number_operator = ( + modes_acted_on_by_term_b[0][0] == modes_acted_on_by_term_b[1][0] or + modes_acted_on_by_term_b[1][1]) + term_c_is_number_operator = ( + modes_acted_on_by_term_c[0][0] == modes_acted_on_by_term_c[1][0] or + modes_acted_on_by_term_c[1][1]) + + # Number operators always commute. + if term_b_is_number_operator and term_c_is_number_operator: + return True + + # If the first commutator's terms are both hopping, and both create + # or annihilate the same mode, then the result is zero. + if not (term_b_is_number_operator or term_c_is_number_operator): + if (modes_acted_on_by_term_b[0][0] == modes_acted_on_by_term_c[0][0] or + modes_acted_on_by_term_b[1][0] == + modes_acted_on_by_term_c[1][0]): + return True + + # The modes term_a acts on are only needed if we reach this stage. + modes_acted_on_by_term_a, = term_a.terms.keys() + modes_touched_b = [modes_acted_on_by_term_b[0][0], + modes_acted_on_by_term_b[1][0]] + modes_touched_bc = [ + modes_acted_on_by_term_b[0][0], modes_acted_on_by_term_b[1][0], + modes_acted_on_by_term_c[0][0], modes_acted_on_by_term_c[1][0]] + + # If the term_a shares no indices with bc, the double commutator is zero. + if not (modes_acted_on_by_term_a[0][0] in modes_touched_bc or + modes_acted_on_by_term_a[1][0] in modes_touched_bc): + return True + + # If term_b and term_c are not both number operators and act on the + # same modes, the commutator is zero. + if (sum(1 for i in modes_touched_b if i in modes_touched_c) > 1 and + (term_b_is_number_operator or term_c_is_number_operator)): + return True + + # Create a list of all the creation and annihilations performed. + all_changes = (modes_acted_on_by_term_a + modes_acted_on_by_term_b + + modes_acted_on_by_term_c) + counts = {} + for operator in all_changes: + counts[operator[0]] = counts.get(operator[0], 0) + 2 * operator[1] - 1 + + # If the final result creates or destroys the same mode twice. + commutes = max(itervalues(counts)) > 1 or min(itervalues(counts)) < -1 + + return commutes + + +def dual_basis_error_operator(terms, indices=None, is_hopping_operator=None, + jellium_only=False, verbose=False): + """Determine the difference between the exact generator of unitary + evolution and the approximate generator given by the second-order + Trotter-Suzuki expansion. + + Args: + terms: a list of FermionOperators in the Hamiltonian in the + order in which they will be simulated. + indices: a set of indices the terms act on in the same order as terms. + is_hopping_operator: a list of whether each term is a hopping operator. + jellium_only: Whether the terms are from the jellium Hamiltonian only, + rather than the full dual basis Hamiltonian (i.e. whether + c_i = c for all number operators i^ i, or whether they + depend on i as is possible in the general case). + verbose: Whether to print percentage progress. + + Returns: + The difference between the true and effective generators of time + evolution for a single Trotter step. + + Notes: follows Equation 9 of Poulin et al.'s work in "The Trotter Step + Size Required for Accurate Quantum Simulation of Quantum Chemistry". + """ + more_info = bool(indices) + n_terms = len(terms) + + if verbose: + import time + start = time.time() + + error_operator = FermionOperator.zero() + for beta in range(n_terms): + if verbose and beta % (n_terms // 30) == 0: + print('%4.3f percent done in' % ( + (float(beta) / n_terms) ** 3 * 100), time.time() - start) + + for alpha in range(beta + 1): + for alpha_prime in range(beta): + # If we have pre-computed info on indices, use it to determine + # trivial double commutation. + if more_info: + if (not + trivially_double_commutes_dual_basis_using_term_info( + indices[alpha], indices[beta], + indices[alpha_prime], is_hopping_operator[alpha], + is_hopping_operator[beta], + is_hopping_operator[alpha_prime], jellium_only)): + # Determine the result of the double commutator. + double_com = double_commutator( + terms[alpha], terms[beta], terms[alpha_prime], + indices[beta], indices[alpha_prime], + is_hopping_operator[beta], + is_hopping_operator[alpha_prime]) + if alpha == beta: + double_com /= 2.0 + + error_operator += double_com + + # If we don't have more info, check for trivial double + # commutation using the terms directly. + elif not trivially_double_commutes_dual_basis( + terms[alpha], terms[beta], terms[alpha_prime]): + double_com = double_commutator( + terms[alpha], terms[beta], terms[alpha_prime]) + + if alpha == beta: + double_com /= 2.0 + + error_operator += double_com + + error_operator /= 12.0 + return error_operator + + +def dual_basis_error_bound(terms, indices=None, is_hopping_operator=None, + jellium_only=False, verbose=False): + """Numerically upper bound the error in the ground state energy + for the second-order Trotter-Suzuki expansion. + + Args: + terms: a list of single-term FermionOperators in the Hamiltonian + to be simulated. + indices: a set of indices the terms act on in the same order as terms. + is_hopping_operator: a list of whether each term is a hopping operator. + jellium_only: Whether the terms are from the jellium Hamiltonian only, + rather than the full dual basis Hamiltonian (i.e. whether + c_i = c for all number operators i^ i, or whether they + depend on i as is possible in the general case). + verbose: Whether to print percentage progress. + + Returns: + A float upper bound on norm of error in the ground state energy. + + Notes: + Follows Equation 9 of Poulin et al.'s work in "The Trotter Step + Size Required for Accurate Quantum Simulation of Quantum + Chemistry" to calculate the error operator. + """ + # Return the 1-norm of the error operator (upper bound on error). + return numpy.sum(numpy.absolute(list(dual_basis_error_operator( + terms, indices, is_hopping_operator, + jellium_only, verbose).terms.values()))) + + +def simulation_ordered_grouped_dual_basis_terms_with_info( + dual_basis_hamiltonian, input_ordering=None): + """Give terms from the dual basis Hamiltonian in simulated order. + + Uses the simulation ordering, grouping terms into hopping + (i^ j + j^ i) and number (i^j^ i j + c_i i^ i + c_j j^ j) operators. + Pre-computes term information (indices each operator acts on, as + well as whether each operator is a hopping operator. + + Args: + dual_basis_hamiltonian (FermionOperator): The Hamiltonian. + input_ordering (list): The initial Jordan-Wigner canonical order. + + Returns: + A 3-tuple of terms from the plane-wave dual basis Hamiltonian in + order of simulation, the indices they act on, and whether they + are hopping operators (both also in the same order). + """ + zero = FermionOperator.zero() + hamiltonian = dual_basis_hamiltonian + n_qubits = count_qubits(hamiltonian) + + ordered_terms = [] + ordered_indices = [] + ordered_is_hopping_operator = [] + + # If no input mode ordering is specified, default to range(n_qubits). + if not input_ordering: + input_ordering = list(range(n_qubits)) + + # Half a second-order Trotter step reverses the input ordering: this tells + # us how much we need to include in the ordered list of terms. + final_ordering = list(reversed(input_ordering)) + + # Follow odd-even transposition sort. In alternating steps, swap each even + # qubits with the odd qubit to its right, and in the next step swap each + # the odd qubits with the even qubit to its right. Do this until the input + # ordering has been reversed. + odd = 0 + while input_ordering != final_ordering: + for i in range(odd, n_qubits - 1, 2): + # Always keep the max on the left to avoid having to normal order. + left = max(input_ordering[i], input_ordering[i + 1]) + right = min(input_ordering[i], input_ordering[i + 1]) + + # Calculate the hopping operators in the Hamiltonian. + left_hopping_operator = FermionOperator( + ((left, 1), (right, 0)), hamiltonian.terms.get( + ((left, 1), (right, 0)), 0.0)) + right_hopping_operator = FermionOperator( + ((right, 1), (left, 0)), hamiltonian.terms.get( + ((right, 1), (left, 0)), 0.0)) + + # Calculate the two-number operator l^ r^ l r in the Hamiltonian. + two_number_operator = FermionOperator( + ((left, 1), (right, 1), (left, 0), (right, 0)), + hamiltonian.terms.get( + ((left, 1), (right, 1), (left, 0), (right, 0)), 0.0)) + + # Calculate the left number operator, left^ left. + left_number_operator = FermionOperator( + ((left, 1), (left, 0)), hamiltonian.terms.get( + ((left, 1), (left, 0)), 0.0)) + + # Calculate the right number operator, right^ right. + right_number_operator = FermionOperator( + ((right, 1), (right, 0)), hamiltonian.terms.get( + ((right, 1), (right, 0)), 0.0)) + + # Divide single-number terms by n_qubits-1 to avoid over-counting. + # Each qubit is swapped n_qubits-1 times total. + left_number_operator /= (n_qubits - 1) + right_number_operator /= (n_qubits - 1) + + # If the overall hopping operator isn't close to zero, append it. + # Include the indices it acts on and that it's a hopping operator. + if not (left_hopping_operator + + right_hopping_operator).isclose(zero): + ordered_terms.append(left_hopping_operator + + right_hopping_operator) + ordered_indices.append(set((left, right))) + ordered_is_hopping_operator.append(True) + + # If the overall number operator isn't close to zero, append it. + # Include the indices it acts on and that it's a number operator. + if not (two_number_operator + left_number_operator + + right_number_operator).isclose(zero): + ordered_terms.append(two_number_operator + + left_number_operator + + right_number_operator) + ordered_indices.append(set((left, right))) + ordered_is_hopping_operator.append(False) + + # Track the current Jordan-Wigner canonical ordering. + input_ordering[i], input_ordering[i + 1] = (input_ordering[i + 1], + input_ordering[i]) + + # Alternate even and odd steps of the reversal procedure. + odd = 1 - odd + + return (ordered_terms, ordered_indices, ordered_is_hopping_operator) + + +def ordered_dual_basis_terms_no_info(dual_basis_hamiltonian): + """Give terms from the dual basis Hamiltonian in dictionary output order. + + Args: + dual_basis_hamiltonian (FermionOperator): The Hamiltonian. + + Returns: + A list of terms from the dual basis Hamiltonian in simulated order. + """ + n_qubits = count_qubits(dual_basis_hamiltonian) + terms = [] + + for operators, coefficient in iteritems(dual_basis_hamiltonian.terms): + terms += [FermionOperator(operators, coefficient)] + + return terms + + +def dual_basis_jellium_hamiltonian(grid_length, dimension=3, + wigner_seitz_radius=10., n_particles=None, + spinless=True): + """Return the jellium Hamiltonian with the given parameters. + + Args: + grid_length (int): The number of spatial orbitals per dimension. + dimension (int): The dimension of the system. + wigner_seitz_radius (float): The radius per particle in Bohr. + n_particles (int): The number of particles in the system. + Defaults to half filling if not specified. + """ + n_qubits = grid_length ** dimension + if not spinless: + n_qubits *= 2 + + if n_particles is None: + # Default to half filling fraction. + n_particles = n_qubits // 2 + + if not (0 <= n_particles <= n_qubits): + raise ValueError('n_particles must be between 0 and the number of' + ' spin-orbitals.') + + # Compute appropriate length scale. + length_scale = wigner_seitz_length_scale( + wigner_seitz_radius, n_particles, dimension) + + grid = Grid(dimension, grid_length, length_scale) + hamiltonian = jellium_model(grid, spinless=spinless, plane_wave=False) + hamiltonian = normal_ordered(hamiltonian) + hamiltonian.compress() + return hamiltonian diff --git a/src/fermilib/utils/_dual_basis_trotter_error_test.py b/src/fermilib/utils/_dual_basis_trotter_error_test.py new file mode 100644 index 0000000..097c626 --- /dev/null +++ b/src/fermilib/utils/_dual_basis_trotter_error_test.py @@ -0,0 +1,482 @@ +# 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. + +"""Tests for _dual_basis_trotter_error.py.""" +import unittest + +from fermilib.ops import FermionOperator +from fermilib.utils._dual_basis_trotter_error import * +from fermilib.utils import Grid, jellium_model, wigner_seitz_length_scale + + +class DoubleCommutatorTest(unittest.TestCase): + def test_double_commutator_no_intersection_with_union_of_second_two(self): + com = double_commutator(FermionOperator('4^ 3^ 6 5'), + FermionOperator('2^ 1 0'), + FermionOperator('0^')) + self.assertTrue(com.isclose(FermionOperator.zero())) + + def test_double_commutator_more_info_not_hopping(self): + com = double_commutator( + FermionOperator('3^ 2'), + FermionOperator('2^ 3') + FermionOperator('3^ 2'), + FermionOperator('4^ 2^ 4 2'), indices2=set([2, 3]), + indices3=set([2, 4]), is_hopping_operator2=True, + is_hopping_operator3=False) + self.assertTrue(com.isclose(FermionOperator('4^ 2^ 4 2') - + FermionOperator('4^ 3^ 4 3'))) + + def test_double_commtator_more_info_both_hopping(self): + com = double_commutator( + FermionOperator('4^ 3^ 4 3'), + FermionOperator('1^ 2', 2.1) + FermionOperator('2^ 1', 2.1), + FermionOperator('1^ 3', -1.3) + FermionOperator('3^ 1', -1.3), + indices2=set([1, 2]), indices3=set([1, 3]), + is_hopping_operator2=True, is_hopping_operator3=True) + self.assertTrue(com.isclose(FermionOperator('4^ 3^ 4 2', 2.73) + + FermionOperator('4^ 2^ 4 3', 2.73))) + + +class TriviallyDoubleCommutesDualBasisUsingTermInfoTest(unittest.TestCase): + def test_number_operators_trivially_commute(self): + self.assertTrue(trivially_double_commutes_dual_basis_using_term_info( + indices_alpha=set([1, 2]), indices_beta=set([3, 4]), + indices_alpha_prime=set([2, 3]), + is_hopping_operator_alpha=False, is_hopping_operator_beta=False, + is_hopping_operator_alpha_prime=False, jellium_only=True)) + + def test_left_hopping_operator_no_trivial_commutation(self): + self.assertFalse(trivially_double_commutes_dual_basis_using_term_info( + indices_alpha=set([1, 2]), indices_beta=set([3, 4]), + indices_alpha_prime=set([2, 3]), + is_hopping_operator_alpha=True, is_hopping_operator_beta=True, + is_hopping_operator_alpha_prime=False, jellium_only=True)) + + def test_right_hopping_operator_no_trivial_commutation(self): + self.assertFalse(trivially_double_commutes_dual_basis_using_term_info( + indices_alpha=set([1, 2]), indices_beta=set([3, 4]), + indices_alpha_prime=set([2, 3]), + is_hopping_operator_alpha=True, is_hopping_operator_beta=False, + is_hopping_operator_alpha_prime=True, jellium_only=True)) + + def test_alpha_is_hopping_operator_others_number_trivial_commutation(self): + self.assertTrue(trivially_double_commutes_dual_basis_using_term_info( + indices_alpha=set([1, 2]), indices_beta=set([3, 4]), + indices_alpha_prime=set([2, 3]), + is_hopping_operator_alpha=True, is_hopping_operator_beta=False, + is_hopping_operator_alpha_prime=False, jellium_only=True)) + + def test_no_intersection_in_first_commutator_trivially_commutes(self): + self.assertTrue(trivially_double_commutes_dual_basis_using_term_info( + indices_alpha=set([1, 2]), indices_beta=set([3, 4]), + indices_alpha_prime=set([1, 2]), + is_hopping_operator_alpha=True, is_hopping_operator_beta=True, + is_hopping_operator_alpha_prime=False, jellium_only=True)) + + def test_double_intersection_in_first_commutator_trivially_commutes(self): + self.assertTrue(trivially_double_commutes_dual_basis_using_term_info( + indices_alpha=set([3, 2]), indices_beta=set([3, 4]), + indices_alpha_prime=set([4, 3]), + is_hopping_operator_alpha=True, is_hopping_operator_beta=True, + is_hopping_operator_alpha_prime=False, jellium_only=True)) + + def test_single_intersection_in_first_commutator_nontrivial(self): + self.assertFalse(trivially_double_commutes_dual_basis_using_term_info( + indices_alpha=set([3, 2]), indices_beta=set([3, 4]), + indices_alpha_prime=set([4, 5]), + is_hopping_operator_alpha=False, is_hopping_operator_beta=True, + is_hopping_operator_alpha_prime=False, jellium_only=True)) + + def test_no_intersection_between_first_and_other_terms_is_trivial(self): + self.assertTrue(trivially_double_commutes_dual_basis_using_term_info( + indices_alpha=set([3, 2]), indices_beta=set([1, 4]), + indices_alpha_prime=set([4, 5]), + is_hopping_operator_alpha=False, is_hopping_operator_beta=True, + is_hopping_operator_alpha_prime=False, jellium_only=True)) + + +class TriviallyCommutesDualBasisTest(unittest.TestCase): + def test_trivially_commutes_no_intersection(self): + self.assertTrue(trivially_commutes_dual_basis( + FermionOperator('3^ 2^ 3 2'), FermionOperator('4^ 1'))) + + def test_no_trivial_commute_with_intersection(self): + self.assertFalse(trivially_commutes_dual_basis( + FermionOperator('2^ 1'), FermionOperator('5^ 2^ 5 2'))) + + def test_trivially_commutes_both_single_number_operators(self): + self.assertTrue(trivially_commutes_dual_basis( + FermionOperator('3^ 3'), FermionOperator('3^ 3'))) + + def test_trivially_commutes_nonintersecting_single_number_operators(self): + self.assertTrue(trivially_commutes_dual_basis( + FermionOperator('2^ 2'), FermionOperator('3^ 3'))) + + def test_trivially_commutes_both_double_number_operators(self): + self.assertTrue(trivially_commutes_dual_basis( + FermionOperator('3^ 2^ 3 2'), FermionOperator('3^ 1^ 3 1'))) + + def test_trivially_commutes_one_double_number_operators(self): + self.assertTrue(trivially_commutes_dual_basis( + FermionOperator('3^ 2^ 3 2'), FermionOperator('3^ 3'))) + + def test_no_trivial_commute_right_hopping_operator(self): + self.assertFalse(trivially_commutes_dual_basis( + FermionOperator('3^ 1^ 3 1'), FermionOperator('3^ 2'))) + + def test_no_trivial_commute_left_hopping_operator(self): + self.assertFalse(trivially_commutes_dual_basis( + FermionOperator('3^ 2'), FermionOperator('3^ 3'))) + + def test_trivially_commutes_both_hopping_create_same_mode(self): + self.assertTrue(trivially_commutes_dual_basis( + FermionOperator('3^ 2'), FermionOperator('3^ 1'))) + + def test_trivially_commutes_both_hopping_annihilate_same_mode(self): + self.assertTrue(trivially_commutes_dual_basis( + FermionOperator('4^ 1'), FermionOperator('3^ 1'))) + + def test_trivially_commutes_both_hopping_and_number_on_same_modes(self): + self.assertTrue(trivially_commutes_dual_basis( + FermionOperator('4^ 1'), FermionOperator('4^ 1^ 4 1'))) + + +class TriviallyDoubleCommutesDualBasisTest(unittest.TestCase): + def test_trivially_double_commutes_no_intersection(self): + self.assertTrue(trivially_double_commutes_dual_basis( + FermionOperator('3^ 4'), + FermionOperator('3^ 2^ 3 2'), FermionOperator('4^ 1'))) + + def test_no_trivial_double_commute_with_intersection(self): + self.assertFalse(trivially_double_commutes_dual_basis( + FermionOperator('4^ 2'), + FermionOperator('2^ 1'), FermionOperator('5^ 2^ 5 2'))) + + def test_trivially_double_commutes_both_single_number_operators(self): + self.assertTrue(trivially_double_commutes_dual_basis( + FermionOperator('4^ 3'), + FermionOperator('3^ 3'), FermionOperator('3^ 3'))) + + def test_trivially_double_commutes_nonintersecting_single_number_ops(self): + self.assertTrue(trivially_double_commutes_dual_basis( + FermionOperator('3^ 2'), + FermionOperator('2^ 2'), FermionOperator('3^ 3'))) + + def test_trivially_double_commutes_both_double_number_operators(self): + self.assertTrue(trivially_double_commutes_dual_basis( + FermionOperator('4^ 3'), + FermionOperator('3^ 2^ 3 2'), FermionOperator('3^ 1^ 3 1'))) + + def test_trivially_double_commutes_one_double_number_operators(self): + self.assertTrue(trivially_double_commutes_dual_basis( + FermionOperator('4^ 3'), + FermionOperator('3^ 2^ 3 2'), FermionOperator('3^ 3'))) + + def test_no_trivial_double_commute_right_hopping_operator(self): + self.assertFalse(trivially_double_commutes_dual_basis( + FermionOperator('4^ 3'), + FermionOperator('3^ 1^ 3 1'), FermionOperator('3^ 2'))) + + def test_no_trivial_double_commute_left_hopping_operator(self): + self.assertFalse(trivially_double_commutes_dual_basis( + FermionOperator('4^ 3'), + FermionOperator('3^ 2'), FermionOperator('3^ 3'))) + + def test_trivially_double_commutes_both_hopping_create_same_mode(self): + self.assertTrue(trivially_double_commutes_dual_basis( + FermionOperator('3^ 3'), + FermionOperator('3^ 2'), FermionOperator('3^ 1'))) + + def test_trivially_double_commutes_both_hopping_annihilate_same_mode(self): + self.assertTrue(trivially_double_commutes_dual_basis( + FermionOperator('1^ 1'), + FermionOperator('4^ 1'), FermionOperator('3^ 1'))) + + def test_trivially_double_commutes_hopping_and_number_on_same_modes(self): + self.assertTrue(trivially_double_commutes_dual_basis( + FermionOperator('4^ 3'), + FermionOperator('4^ 1'), FermionOperator('4^ 1^ 4 1'))) + + def test_trivially_double_commutes_no_intersection_a_with_bc(self): + self.assertTrue(trivially_double_commutes_dual_basis( + FermionOperator('5^ 2'), + FermionOperator('3^ 1'), FermionOperator('4^ 1^ 4 1'))) + + def test_trivially_double_commutes_double_create_in_a_and_b(self): + self.assertTrue(trivially_double_commutes_dual_basis( + FermionOperator('5^ 2'), + FermionOperator('3^ 1'), FermionOperator('4^ 1^ 4 1'))) + + def test_trivially_double_commutes_double_annihilate_in_a_and_c(self): + self.assertTrue(trivially_double_commutes_dual_basis( + FermionOperator('5^ 2'), + FermionOperator('3^ 1'), FermionOperator('4^ 1^ 4 1'))) + + def test_no_trivial_double_commute_double_annihilate_with_create(self): + self.assertFalse(trivially_double_commutes_dual_basis( + FermionOperator('5^ 2'), + FermionOperator('2^ 1'), FermionOperator('4^ 2'))) + + def test_trivially_double_commutes_excess_create(self): + self.assertTrue(trivially_double_commutes_dual_basis( + FermionOperator('5^ 2'), + FermionOperator('5^ 5'), FermionOperator('5^ 1'))) + + def test_trivially_double_commutes_excess_annihilate(self): + self.assertTrue(trivially_double_commutes_dual_basis( + FermionOperator('5^ 2'), + FermionOperator('3^ 2'), FermionOperator('2^ 2'))) + + +class ErrorOperatorTest(unittest.TestCase): + def test_error_operator(self): + FO = FermionOperator + + terms = [] + for i in range(4): + terms.append(FO(((i, 1), (i, 0)), 0.018505508252)) + terms.append(FO(((i, 1), ((i + 1) % 4, 0)), -0.0123370055014)) + terms.append(FO(((i, 1), ((i + 2) % 4, 0)), 0.00616850275068)) + terms.append(FO(((i, 1), ((i + 3) % 4, 0)), -0.0123370055014)) + terms.append(normal_ordered(FO(((i, 1), ((i + 1) % 4, 1), + (i, 0), ((i + 1) % 4, 0)), + 3.18309886184))) + if i // 2: + terms.append(normal_ordered( + FO(((i, 1), ((i + 2) % 4, 1), (i, 0), ((i + 2) % 4, 0)), + 22.2816920329))) + + self.assertAlmostEqual( + dual_basis_error_operator(terms, jellium_only=True).terms[ + ((3, 1), (2, 1), (1, 1), (2, 0), (1, 0), (0, 0))], + -0.562500000003) + + +class ErrorBoundTest(unittest.TestCase): + def setUp(self): + FO = FermionOperator + + self.terms = [] + for i in range(4): + self.terms.append(FO(((i, 1), (i, 0)), 0.018505508252)) + self.terms.append(FO(((i, 1), ((i + 1) % 4, 0)), -0.0123370055014)) + self.terms.append(FO(((i, 1), ((i + 2) % 4, 0)), 0.00616850275068)) + self.terms.append(FO(((i, 1), ((i + 3) % 4, 0)), -0.0123370055014)) + self.terms.append(normal_ordered(FO(((i, 1), ((i + 1) % 4, 1), + (i, 0), ((i + 1) % 4, 0)), + 3.18309886184))) + if i // 2: + self.terms.append(normal_ordered( + FO(((i, 1), ((i + 2) % 4, 1), (i, 0), ((i + 2) % 4, 0)), + 22.2816920329))) + + def test_error_bound(self): + self.assertAlmostEqual(dual_basis_error_bound( + self.terms, jellium_only=True), 6.92941899358) + + def test_error_bound_using_info_1d(self): + # Generate the Hamiltonian. + hamiltonian = dual_basis_jellium_hamiltonian(grid_length=4, + dimension=1) + + # Unpack result into terms, indices they act on, and whether they're + # hopping operators. + result = simulation_ordered_grouped_dual_basis_terms_with_info( + hamiltonian) + terms, indices, is_hopping = result + self.assertAlmostEqual(dual_basis_error_bound( + terms, indices, is_hopping), 7.4239378440953283) + + def test_error_bound_using_info_2d_verbose(self): + # Generate the Hamiltonian. + hamiltonian = dual_basis_jellium_hamiltonian(grid_length=3, + dimension=2) + + # Unpack result into terms, indices they act on, and whether they're + # hopping operators. + result = simulation_ordered_grouped_dual_basis_terms_with_info( + hamiltonian) + terms, indices, is_hopping = result + self.assertAlmostEqual(0.052213321121580794, dual_basis_error_bound( + terms, indices, is_hopping, jellium_only=True, verbose=True)) + + +class OrderedDualBasisTermsMoreInfoTest(unittest.TestCase): + def test_sum_of_ordered_terms_equals_full_hamiltonian(self): + grid_length = 4 + dimension = 2 + wigner_seitz_radius = 10.0 + inverse_filling_fraction = 2 + n_qubits = grid_length ** dimension + + # Compute appropriate length scale. + n_particles = n_qubits // inverse_filling_fraction + + # Generate the Hamiltonian. + hamiltonian = dual_basis_jellium_hamiltonian( + grid_length, dimension, wigner_seitz_radius, n_particles) + + terms = simulation_ordered_grouped_dual_basis_terms_with_info( + hamiltonian)[0] + terms_total = sum(terms, FermionOperator.zero()) + + length_scale = wigner_seitz_length_scale( + wigner_seitz_radius, n_particles, dimension) + + grid = Grid(dimension, grid_length, length_scale) + hamiltonian = jellium_model(grid, spinless=True, plane_wave=False) + hamiltonian = normal_ordered(hamiltonian) + self.assertTrue(terms_total.isclose(hamiltonian)) + + def test_correct_indices_terms_with_info(self): + grid_length = 4 + dimension = 1 + wigner_seitz_radius = 10.0 + inverse_filling_fraction = 2 + n_qubits = grid_length ** dimension + + # Compute appropriate length scale. + n_particles = n_qubits // inverse_filling_fraction + + # Generate the Hamiltonian. + hamiltonian = dual_basis_jellium_hamiltonian( + grid_length, dimension, wigner_seitz_radius, n_particles) + + # Unpack result into terms, indices they act on, and whether they're + # hopping operators. + result = simulation_ordered_grouped_dual_basis_terms_with_info( + hamiltonian) + terms, indices, is_hopping = result + + for i in range(len(terms)): + term = list(terms[i].terms) + term_indices = set() + for single_term in term: + term_indices = term_indices.union( + [single_term[j][0] for j in range(len(single_term))]) + self.assertEqual(term_indices, indices[i]) + + def test_is_hopping_operator_terms_with_info(self): + grid_length = 4 + dimension = 1 + wigner_seitz_radius = 10.0 + inverse_filling_fraction = 2 + n_qubits = grid_length ** dimension + + # Compute appropriate length scale. + n_particles = n_qubits // inverse_filling_fraction + + hamiltonian = dual_basis_jellium_hamiltonian( + grid_length, dimension, wigner_seitz_radius, n_particles) + + # Unpack result into terms, indices they act on, and whether they're + # hopping operators. + result = simulation_ordered_grouped_dual_basis_terms_with_info( + hamiltonian) + terms, indices, is_hopping = result + + for i in range(len(terms)): + single_term = list(terms[i].terms)[0] + is_hopping_term = not (single_term[1][1] or + single_term[0][0] == single_term[1][0]) + self.assertEqual(is_hopping_term, is_hopping[i]) + + def test_total_length(self): + grid_length = 8 + dimension = 1 + wigner_seitz_radius = 10.0 + inverse_filling_fraction = 2 + n_qubits = grid_length ** dimension + + # Compute appropriate length scale. + n_particles = n_qubits // inverse_filling_fraction + + hamiltonian = dual_basis_jellium_hamiltonian( + grid_length, dimension, wigner_seitz_radius, n_particles) + + # Unpack result into terms, indices they act on, and whether they're + # hopping operators. + result = simulation_ordered_grouped_dual_basis_terms_with_info( + hamiltonian) + terms, indices, is_hopping = result + + self.assertEqual(len(terms), n_qubits * (n_qubits - 1)) + + +class OrderedDualBasisTermsNoInfoTest(unittest.TestCase): + def test_all_terms_in_dual_basis_jellium_hamiltonian(self): + grid_length = 4 + dimension = 1 + + # Generate the Hamiltonian. + hamiltonian = dual_basis_jellium_hamiltonian(grid_length, dimension) + + terms = ordered_dual_basis_terms_no_info(hamiltonian) + FO = FermionOperator + + expected_terms = [] + for i in range(grid_length ** dimension): + expected_terms.append(FO(((i, 1), (i, 0)), + 0.018505508252)) + expected_terms.append(FO(((i, 1), ((i + 1) % 4, 0)), + -0.0123370055014)) + expected_terms.append(FO(((i, 1), ((i + 2) % 4, 0)), + 0.00616850275068)) + expected_terms.append(FO(((i, 1), ((i + 3) % 4, 0)), + -0.0123370055014)) + expected_terms.append(normal_ordered( + FO(((i, 1), ((i + 1) % 4, 1), (i, 0), ((i + 1) % 4, 0)), + 3.18309886184))) + if i // 2: + expected_terms.append(normal_ordered( + FO(((i, 1), ((i + 2) % 4, 1), (i, 0), ((i + 2) % 4, 0)), + 22.2816920329))) + + for term in terms: + found_in_other = False + for term2 in expected_terms: + if term.isclose(term2, rel_tol=1e-8): + self.assertFalse(found_in_other) + found_in_other = True + self.assertTrue(found_in_other, msg=str(term)) + for term in expected_terms: + found_in_other = False + for term2 in terms: + if term.isclose(term2, rel_tol=1e-8): + self.assertFalse(found_in_other) + found_in_other = True + self.assertTrue(found_in_other, msg=str(term)) + + def test_sum_of_ordered_terms_equals_full_hamiltonian(self): + grid_length = 4 + dimension = 1 + wigner_seitz_radius = 10.0 + inverse_filling_fraction = 2 + n_qubits = grid_length ** dimension + + # Compute appropriate length scale. + n_particles = n_qubits // inverse_filling_fraction + length_scale = wigner_seitz_length_scale( + wigner_seitz_radius, n_particles, dimension) + + hamiltonian = dual_basis_jellium_hamiltonian(grid_length, dimension) + terms = ordered_dual_basis_terms_no_info(hamiltonian) + terms_total = sum(terms, FermionOperator.zero()) + + grid = Grid(dimension, grid_length, length_scale) + hamiltonian = jellium_model(grid, spinless=True, plane_wave=False) + hamiltonian = normal_ordered(hamiltonian) + self.assertTrue(terms_total.isclose(hamiltonian)) + + +if __name__ == '__main__': + unittest.main() diff --git a/src/fermilib/utils/_graph_test.py b/src/fermilib/utils/_graph_test.py index c4acfa4..c65754b 100644 --- a/src/fermilib/utils/_graph_test.py +++ b/src/fermilib/utils/_graph_test.py @@ -98,7 +98,3 @@ def test_ring(self): self.assertFalse(eight_node.is_adjacent(0, 6)) self.assertEqual(eight_node.shortest_path(0, 6), [0, 1, 2, 3, 4, 5, 6]) - -# Run test. -if __name__ == '__main__': - unittest.main() diff --git a/src/fermilib/utils/_grid_test.py b/src/fermilib/utils/_grid_test.py index 1222290..f3a6018 100644 --- a/src/fermilib/utils/_grid_test.py +++ b/src/fermilib/utils/_grid_test.py @@ -57,8 +57,3 @@ def test_properties(self): (2, 1), (2, 2), ]) - - -# Run test. -if __name__ == '__main__': - unittest.main() diff --git a/src/fermilib/utils/_hubbard_test.py b/src/fermilib/utils/_hubbard_test.py index 7290a24..aadff32 100644 --- a/src/fermilib/utils/_hubbard_test.py +++ b/src/fermilib/utils/_hubbard_test.py @@ -101,8 +101,3 @@ def test_two_by_two_spinless_periodic_rudimentary(self): self.x_dimension, self.y_dimension, self.tunneling, self.coulomb, self.chemical_potential, self.magnetic_field, periodic=True, spinless=True) - - -# Run test. -if __name__ == '__main__': - unittest.main() diff --git a/src/fermilib/utils/_jellium.py b/src/fermilib/utils/_jellium.py index da37108..29d183f 100644 --- a/src/fermilib/utils/_jellium.py +++ b/src/fermilib/utils/_jellium.py @@ -14,6 +14,7 @@ from __future__ import absolute_import import numpy + from projectq.ops import QubitOperator from fermilib.ops import FermionOperator @@ -141,8 +142,8 @@ def momentum_vector(momentum_indices, grid): return 2. * numpy.pi * adjusted_vector / grid.scale -def momentum_kinetic_operator(grid, spinless=False): - """Return the kinetic energy operator in momentum second quantization. +def plane_wave_kinetic(grid, spinless=False): + """Return the kinetic energy operator in the plane wave basis. Args: grid (fermilib.utils.Grid): The discretization to use. @@ -153,10 +154,7 @@ def momentum_kinetic_operator(grid, spinless=False): """ # Initialize. operator = FermionOperator() - if spinless: - spins = [None] - else: - spins = [0, 1] + spins = [None] if spinless else [0, 1] # Loop once through all plane waves. for momenta_indices in grid.all_points_indices(): @@ -174,8 +172,8 @@ def momentum_kinetic_operator(grid, spinless=False): return operator -def momentum_potential_operator(grid, spinless=False): - """Return the potential operator in momentum second quantization. +def plane_wave_potential(grid, spinless=False): + """Return the potential operator in the plane wave basis. Args: grid (Grid): The discretization to use. @@ -185,15 +183,34 @@ def momentum_potential_operator(grid, spinless=False): operator (FermionOperator) """ # Initialize. - volume = grid.volume_scale() - prefactor = 2. * numpy.pi / volume + prefactor = 2. * numpy.pi / grid.volume_scale() operator = FermionOperator((), 0.0) spins = [None] if spinless else [0, 1] + # Pre-Computations. + shifted_omega_indices_dict = {} + shifted_indices_minus_dict = {} + shifted_indices_plus_dict = {} + orbital_ids = {} + for indices_a in grid.all_points_indices(): + shifted_omega_indices = [j - grid.length // 2 for j in indices_a] + shifted_omega_indices_dict[indices_a] = shifted_omega_indices + shifted_indices_minus_dict[indices_a] = {} + shifted_indices_plus_dict[indices_a] = {} + for indices_b in grid.all_points_indices(): + shifted_indices_minus_dict[indices_a][indices_b] = tuple([ + (indices_b[i] - shifted_omega_indices[i]) % grid.length + for i in range(grid.dimensions)]) + shifted_indices_plus_dict[indices_a][indices_b] = tuple([ + (indices_b[i] + shifted_omega_indices[i]) % grid.length + for i in range(grid.dimensions)]) + orbital_ids[indices_a] = {} + for spin in spins: + orbital_ids[indices_a][spin] = orbital_id(grid, indices_a, spin) + # Loop once through all plane waves. for omega_indices in grid.all_points_indices(): - shifted_omega_indices = [index - grid.length // 2 for - index in omega_indices] + shifted_omega_indices = shifted_omega_indices_dict[omega_indices] # Get the momenta vectors. omega_momenta = momentum_vector(omega_indices, grid) @@ -206,22 +223,19 @@ def momentum_potential_operator(grid, spinless=False): coefficient = prefactor / omega_momenta.dot(omega_momenta) for grid_indices_a in grid.all_points_indices(): - shifted_indices_d = [ - (grid_indices_a[i] - shifted_omega_indices[i]) % grid.length - for i in range(grid.dimensions)] + shifted_indices_d = ( + shifted_indices_minus_dict[omega_indices][grid_indices_a]) for grid_indices_b in grid.all_points_indices(): - shifted_indices_c = [ - (grid_indices_b[i] + shifted_omega_indices[i]) % - grid.length - for i in range(grid.dimensions)] + shifted_indices_c = ( + shifted_indices_plus_dict[omega_indices][grid_indices_b]) # Loop over spins. for spin_a in spins: - orbital_a = orbital_id(grid, grid_indices_a, spin_a) - orbital_d = orbital_id(grid, shifted_indices_d, spin_a) + orbital_a = orbital_ids[grid_indices_a][spin_a] + orbital_d = orbital_ids[shifted_indices_d][spin_a] for spin_b in spins: - orbital_b = orbital_id(grid, grid_indices_b, spin_b) - orbital_c = orbital_id(grid, shifted_indices_c, spin_b) + orbital_b = orbital_ids[grid_indices_b][spin_b] + orbital_c = orbital_ids[shifted_indices_c][spin_b] # Add interaction term. if ((orbital_a != orbital_b) and @@ -234,52 +248,95 @@ def momentum_potential_operator(grid, spinless=False): return operator -def position_kinetic_operator(grid, spinless=False): - """Return the kinetic operator in position space second quantization. +def dual_basis_jellium_model(grid, spinless=False, + kinetic=True, potential=True, + include_constant=False): + """Return jellium Hamiltonian in the dual basis of arXiv:1706.00023 Args: grid (Grid): The discretization to use. spinless (bool): Whether to use the spinless model or not. + kinetic (bool): Whether to include kinetic terms. + potential (bool): Whether to include potential terms. + include_constant (bool): Whether to include the Madelung constant. Returns: operator (FermionOperator) """ # Initialize. n_points = grid.num_points() + position_prefactor = 2. * numpy.pi / grid.volume_scale() operator = FermionOperator() spins = [None] if spinless else [0, 1] + # Pre-Computations. + position_vectors = {} + momentum_vectors = {} + momenta_squared_dict = {} + orbital_ids = {} + for indices in grid.all_points_indices(): + position_vectors[indices] = position_vector(indices, grid) + momenta = momentum_vector(indices, grid) + momentum_vectors[indices] = momenta + momenta_squared_dict[indices] = momenta.dot(momenta) + orbital_ids[indices] = {} + for spin in spins: + orbital_ids[indices][spin] = orbital_id(grid, indices, spin) + # Loop once through all lattice sites. for grid_indices_a in grid.all_points_indices(): - coordinates_a = position_vector(grid_indices_a, grid) + coordinates_a = position_vectors[grid_indices_a] for grid_indices_b in grid.all_points_indices(): - coordinates_b = position_vector(grid_indices_b, grid) + coordinates_b = position_vectors[grid_indices_b] differences = coordinates_b - coordinates_a - # Compute coefficient. - coefficient = 0. + # Compute coefficients. + kinetic_coefficient = 0. + potential_coefficient = 0. for momenta_indices in grid.all_points_indices(): - momenta = momentum_vector(momenta_indices, grid) - if momenta.any(): - coefficient += ( - numpy.cos(momenta.dot(differences)) * - momenta.dot(momenta) / (2. * float(n_points))) + momenta = momentum_vectors[momenta_indices] + momenta_squared = momenta_squared_dict[momenta_indices] + if momenta_squared == 0: + continue + cos_difference = numpy.cos(momenta.dot(differences)) + if kinetic: + kinetic_coefficient += ( + cos_difference * momenta_squared / + (2. * float(n_points))) + if potential: + potential_coefficient += ( + position_prefactor * cos_difference / momenta_squared) # Loop over spins and identify interacting orbitals. + orbital_a = {} + orbital_b = {} for spin in spins: - orbital_a = orbital_id(grid, grid_indices_a, spin) - orbital_b = orbital_id(grid, grid_indices_b, spin) - - # Add interaction term. - operators = ((orbital_a, 1), (orbital_b, 0)) - operator += FermionOperator(operators, coefficient) + orbital_a[spin] = orbital_ids[grid_indices_a][spin] + orbital_b[spin] = orbital_ids[grid_indices_b][spin] + if kinetic: + for spin in spins: + operators = ((orbital_a[spin], 1), (orbital_b[spin], 0)) + operator += FermionOperator(operators, kinetic_coefficient) + if potential: + for sa in spins: + for sb in spins: + if orbital_a[sa] == orbital_b[sb]: + continue + operators = ((orbital_a[sa], 1), (orbital_a[sa], 0), + (orbital_b[sb], 1), (orbital_b[sb], 0)) + operator += FermionOperator(operators, + potential_coefficient) + + # Include the Madelung constant if requested. + if include_constant: + operator += FermionOperator.identity() * (2.8372 / grid.scale) # Return. return operator -def position_potential_operator(grid, spinless=False): - """Return the potential operator in position space second quantization. +def dual_basis_kinetic(grid, spinless=False): + """Return the kinetic operator in the dual basis of arXiv:1706.00023. Args: grid (Grid): The discretization to use. @@ -288,70 +345,55 @@ def position_potential_operator(grid, spinless=False): Returns: operator (FermionOperator) """ - # Initialize. - volume = grid.volume_scale() - prefactor = 2. * numpy.pi / volume - operator = FermionOperator() - spins = [None] if spinless else [0, 1] - - # Loop once through all lattice sites. - for grid_indices_a in grid.all_points_indices(): - coordinates_a = position_vector(grid_indices_a, grid) - for grid_indices_b in grid.all_points_indices(): - coordinates_b = position_vector(grid_indices_b, grid) - differences = coordinates_b - coordinates_a + return dual_basis_jellium_model(grid, spinless, True, False) - # Compute coefficient. - coefficient = 0. - for momenta_indices in grid.all_points_indices(): - momenta = momentum_vector(momenta_indices, grid) - if momenta.any(): - coefficient += ( - prefactor * numpy.cos(momenta.dot(differences)) / - momenta.dot(momenta)) - # Loop over spins and identify interacting orbitals. - for spin_a in spins: - orbital_a = orbital_id(grid, grid_indices_a, spin_a) - for spin_b in spins: - orbital_b = orbital_id(grid, grid_indices_b, spin_b) +def dual_basis_potential(grid, spinless=False): + """Return the potential operator in the dual basis of arXiv:1706.00023 - # Add interaction term. - if orbital_a != orbital_b: - operators = ((orbital_a, 1), (orbital_a, 0), - (orbital_b, 1), (orbital_b, 0)) - operator += FermionOperator(operators, coefficient) + Args: + grid (Grid): The discretization to use. + spinless (bool): Whether to use the spinless model or not. - return operator + Returns: + operator (FermionOperator) + """ + return dual_basis_jellium_model(grid, spinless, False, True) -def jellium_model(grid, spinless=False, momentum_space=True): +def jellium_model(grid, spinless=False, plane_wave=True, + include_constant=False): """Return jellium Hamiltonian as FermionOperator class. Args: grid (fermilib.utils.Grid): The discretization to use. spinless (bool): Whether to use the spinless model or not. - momentum_space (bool): Whether to return in momentum space (True) + plane_wave (bool): Whether to return in momentum space (True) or position space (False). + include_constant (bool): Whether to include the Madelung constant. Returns: FermionOperator: The Hamiltonian of the model. """ - if momentum_space: - hamiltonian = momentum_kinetic_operator(grid, spinless) - hamiltonian += momentum_potential_operator(grid, spinless) + if plane_wave: + hamiltonian = plane_wave_kinetic(grid, spinless) + hamiltonian += plane_wave_potential(grid, spinless) else: - hamiltonian = position_kinetic_operator(grid, spinless) - hamiltonian += position_potential_operator(grid, spinless) + hamiltonian = dual_basis_jellium_model(grid, spinless) + # Include the Madelung constant if requested. + if include_constant: + hamiltonian += FermionOperator.identity() * (2.8372 / grid.scale) return hamiltonian -def jordan_wigner_position_jellium(grid, spinless=False): - """Return the position space jellium Hamiltonian as QubitOperator. +def jordan_wigner_dual_basis_jellium(grid, spinless=False, + include_constant=False): + """Return the jellium Hamiltonian as QubitOperator in the dual basis. Args: grid (Grid): The discretization to use. spinless (bool): Whether to use the spinless model or not. + include_constant (bool): Whether to include the Madelung constant. Returns: hamiltonian (QubitOperator) @@ -365,11 +407,19 @@ def jordan_wigner_position_jellium(grid, spinless=False): n_qubits = 2 * n_orbitals hamiltonian = QubitOperator() + # Compute vectors. + momentum_vectors = {} + momenta_squared_dict = {} + for indices in grid.all_points_indices(): + momenta = momentum_vector(indices, grid) + momentum_vectors[indices] = momenta + momenta_squared_dict[indices] = momenta.dot(momenta) + # Compute the identity coefficient and the coefficient of local Z terms. identity_coefficient = 0. z_coefficient = 0. for k_indices in grid.all_points_indices(): - momenta = momentum_vector(k_indices, grid) + momenta = momentum_vectors[k_indices] if momenta.any(): momenta_squared = momenta.dot(momenta) identity_coefficient += momenta_squared / 2. @@ -407,18 +457,19 @@ def jordan_wigner_position_jellium(grid, spinless=False): zpzq_coefficient = 0. term_coefficient = 0. for k_indices in grid.all_points_indices(): - momenta = momentum_vector(k_indices, grid) - if momenta.any(): - momenta_squared = momenta.dot(momenta) - cos_difference = numpy.cos(momenta.dot(difference)) + momenta = momentum_vectors[k_indices] + momenta_squared = momenta_squared_dict[k_indices] + if momenta_squared == 0: + continue + cos_difference = numpy.cos(momenta.dot(difference)) - zpzq_coefficient += (zz_prefactor * cos_difference / - momenta_squared) + zpzq_coefficient += (zz_prefactor * cos_difference / + momenta_squared) - if skip_xzx_yzy: - continue - term_coefficient += (xzx_yzy_prefactor * cos_difference * - momenta_squared) + if skip_xzx_yzy: + continue + term_coefficient += (xzx_yzy_prefactor * cos_difference * + momenta_squared) # Add ZZ term. qubit_term = QubitOperator(((p, 'Z'), (q, 'Z')), zpzq_coefficient) @@ -433,5 +484,9 @@ def jordan_wigner_position_jellium(grid, spinless=False): hamiltonian += QubitOperator(xzx_operators, term_coefficient) hamiltonian += QubitOperator(yzy_operators, term_coefficient) + # Include the Madelung constant if requested. + if include_constant: + hamiltonian += QubitOperator((),) * (2.8372 / grid.scale) + # Return Hamiltonian. return hamiltonian diff --git a/src/fermilib/utils/_jellium_hf_state.py b/src/fermilib/utils/_jellium_hf_state.py new file mode 100644 index 0000000..a29c69b --- /dev/null +++ b/src/fermilib/utils/_jellium_hf_state.py @@ -0,0 +1,93 @@ +# 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 constructs the uniform electron gas' Hartree-Fock state.""" +from __future__ import absolute_import + +from fermilib.ops import FermionOperator, normal_ordered +from fermilib.utils import jellium_model, inverse_fourier_transform + +from scipy.sparse import csr_matrix + +import numpy + + +def hartree_fock_state_jellium(grid, n_electrons, spinless=True, + plane_wave=False): + """Give the Hartree-Fock state of jellium. + + Args: + grid (Grid): The discretization to use. + n_electrons (int): Number of electrons in the system. + spinless (bool): Whether to use the spinless model or not. + plane_wave (bool): Whether to return the Hartree-Fock state in + the plane wave (True) or dual basis (False). + + Notes: + The jellium model is built up by filling the lowest-energy + single-particle states in the plane-wave Hamiltonian until + n_electrons states are filled. + """ + # Get the jellium Hamiltonian in the plane wave basis. + hamiltonian = jellium_model(grid, spinless, plane_wave=True) + hamiltonian = normal_ordered(hamiltonian) + hamiltonian.compress() + + # Enumerate the single-particle states. + n_single_particle_states = (grid.length ** grid.dimensions) + if not spinless: + n_single_particle_states *= 2 + + # Compute the energies for each of the single-particle states. + single_particle_energies = numpy.zeros(n_single_particle_states, + dtype=float) + for i in range(n_single_particle_states): + single_particle_energies[i] = hamiltonian.terms.get( + ((i, 1), (i, 0)), 0.0) + + # The number of occupied single-particle states is the number of electrons. + # Those states with the lowest single-particle energies are occupied first. + occupied_states = single_particle_energies.argsort()[:n_electrons] + + if plane_wave: + # In the plane wave basis the HF state is a single determinant. + hartree_fock_state_index = numpy.sum(2 ** occupied_states) + hartree_fock_state = csr_matrix( + ([1.0], ([hartree_fock_state_index], [0])), + shape=(2 ** n_single_particle_states, 1)) + + else: + # Inverse Fourier transform the creation operators for the state to get + # to the dual basis state, then use that to get the dual basis state. + hartree_fock_state_creation_operator = FermionOperator.identity() + for state in occupied_states[::-1]: + hartree_fock_state_creation_operator *= ( + FermionOperator(((int(state), 1),))) + dual_basis_hf_creation_operator = inverse_fourier_transform( + hartree_fock_state_creation_operator, grid, spinless) + + dual_basis_hf_creation = normal_ordered( + dual_basis_hf_creation_operator) + + # Initialize the HF state as a sparse matrix. + hartree_fock_state = csr_matrix( + ([], ([], [])), shape=(2 ** n_single_particle_states, 1), + dtype=complex) + + # Populate the elements of the HF state in the dual basis. + for term in dual_basis_hf_creation.terms: + index = 0 + for operator in term: + index += 2 ** operator[0] + hartree_fock_state[index, 0] = dual_basis_hf_creation.terms[term] + + return hartree_fock_state diff --git a/src/fermilib/utils/_jellium_hf_state_test.py b/src/fermilib/utils/_jellium_hf_state_test.py new file mode 100644 index 0000000..ab1bf18 --- /dev/null +++ b/src/fermilib/utils/_jellium_hf_state_test.py @@ -0,0 +1,99 @@ +"""Tests for _jellium_hf_state.py.""" +from __future__ import absolute_import + +from itertools import permutations +from scipy.sparse import csr_matrix + +import numpy +import unittest + +from fermilib.transforms import get_sparse_operator +from fermilib.utils import expectation, get_ground_state, Grid, jellium_model +from fermilib.utils._plane_wave_hamiltonian import wigner_seitz_length_scale +from fermilib.utils._sparse_tools import jw_number_restrict_operator + +from fermilib.utils._jellium_hf_state import hartree_fock_state_jellium + + +class JelliumHartreeFockStateTest(unittest.TestCase): + def test_hf_state_energy_close_to_ground_energy_at_high_density(self): + grid_length = 8 + dimension = 1 + spinless = True + n_particles = grid_length ** dimension // 2 + + # High density -> small length_scale. + length_scale = 0.25 + + grid = Grid(dimension, grid_length, length_scale) + hamiltonian = jellium_model(grid, spinless) + hamiltonian_sparse = get_sparse_operator(hamiltonian) + + hf_state = hartree_fock_state_jellium(grid, n_particles, + spinless, plane_wave=True) + + restricted_hamiltonian = jw_number_restrict_operator( + hamiltonian_sparse, n_particles) + + E_g = get_ground_state(restricted_hamiltonian)[0] + E_HF_plane_wave = expectation(hamiltonian_sparse, hf_state) + + self.assertAlmostEqual(E_g, E_HF_plane_wave, places=5) + + def test_hf_state_energy_same_in_plane_wave_and_dual_basis(self): + grid_length = 4 + dimension = 1 + wigner_seitz_radius = 10.0 + spinless = False + + n_orbitals = grid_length ** dimension + if not spinless: + n_orbitals *= 2 + n_particles = n_orbitals // 2 + + length_scale = wigner_seitz_length_scale( + wigner_seitz_radius, n_particles, dimension) + + grid = Grid(dimension, grid_length, length_scale) + hamiltonian = jellium_model(grid, spinless) + hamiltonian_dual_basis = jellium_model( + grid, spinless, plane_wave=False) + + # Get the Hamiltonians as sparse operators. + hamiltonian_sparse = get_sparse_operator(hamiltonian) + hamiltonian_dual_sparse = get_sparse_operator(hamiltonian_dual_basis) + + hf_state = hartree_fock_state_jellium( + grid, n_particles, spinless, plane_wave=True) + hf_state_dual = hartree_fock_state_jellium( + grid, n_particles, spinless, plane_wave=False) + + E_HF_plane_wave = expectation(hamiltonian_sparse, hf_state) + E_HF_dual = expectation(hamiltonian_dual_sparse, hf_state_dual) + + self.assertAlmostEqual(E_HF_dual, E_HF_plane_wave) + + def test_hf_state_plane_wave_basis_lowest_single_determinant_state(self): + grid_length = 7 + dimension = 1 + spinless = True + n_particles = 4 + length_scale = 2.0 + + grid = Grid(dimension, grid_length, length_scale) + hamiltonian = jellium_model(grid, spinless) + hamiltonian_sparse = get_sparse_operator(hamiltonian) + + hf_state = hartree_fock_state_jellium(grid, n_particles, + spinless, plane_wave=True) + + HF_energy = expectation(hamiltonian_sparse, hf_state) + + for occupied_orbitals in permutations( + [1] * n_particles + [0] * (grid_length - n_particles)): + state_index = numpy.sum(2 ** numpy.array(occupied_orbitals)) + HF_competitor = csr_matrix(([1.0], ([state_index], [0])), + shape=(2 ** grid_length, 1)) + + self.assertLessEqual( + HF_energy, expectation(hamiltonian_sparse, HF_competitor)) diff --git a/src/fermilib/utils/_jellium_test.py b/src/fermilib/utils/_jellium_test.py index 632311b..ed6fcf8 100644 --- a/src/fermilib/utils/_jellium_test.py +++ b/src/fermilib/utils/_jellium_test.py @@ -16,21 +16,25 @@ import numpy +from fermilib.ops import FermionOperator from fermilib.transforms import jordan_wigner from fermilib.utils import count_qubits, eigenspectrum, Grid from fermilib.utils._jellium import ( + dual_basis_jellium_model, + dual_basis_kinetic, + dual_basis_potential, jellium_model, - jordan_wigner_position_jellium, - momentum_kinetic_operator, - momentum_potential_operator, + jordan_wigner_dual_basis_jellium, momentum_vector, orbital_id, OrbitalSpecificationError, - position_kinetic_operator, - position_potential_operator, + plane_wave_kinetic, + plane_wave_potential, position_vector, ) +from projectq.ops import QubitOperator + class JelliumTest(unittest.TestCase): @@ -117,8 +121,8 @@ def test_kinetic_integration(self): # Compute kinetic energy operator in both momentum and position space. grid = Grid(dimensions=2, length=2, scale=3.) spinless = False - momentum_kinetic = momentum_kinetic_operator(grid, spinless) - position_kinetic = position_kinetic_operator(grid, spinless) + momentum_kinetic = plane_wave_kinetic(grid, spinless) + position_kinetic = dual_basis_kinetic(grid, spinless) # Diagonalize and confirm the same energy. jw_momentum = jordan_wigner(momentum_kinetic) @@ -136,8 +140,8 @@ def test_potential_integration(self): # Compute potential energy operator in momentum and position space. grid = Grid(dimensions=2, length=3, scale=2.) spinless = 1 - momentum_potential = momentum_potential_operator(grid, spinless) - position_potential = position_potential_operator(grid, spinless) + momentum_potential = plane_wave_potential(grid, spinless) + position_potential = dual_basis_potential(grid, spinless) # Diagonalize and confirm the same energy. jw_momentum = jordan_wigner(momentum_potential) @@ -169,6 +173,31 @@ def test_model_integration(self): numpy.absolute(momentum_spectrum - position_spectrum)) self.assertAlmostEqual(difference, 0.) + def test_model_integration_with_constant(self): + # Compute Hamiltonian in both momentum and position space. + length_scale = 0.7 + + grid = Grid(dimensions=2, length=3, scale=length_scale) + spinless = True + + # Include the Madelung constant in the momentum but not the position + # Hamiltonian. + momentum_hamiltonian = jellium_model(grid, spinless, True, + include_constant=True) + position_hamiltonian = jellium_model(grid, spinless, False) + + # Diagonalize and confirm the same energy. + jw_momentum = jordan_wigner(momentum_hamiltonian) + jw_position = jordan_wigner(position_hamiltonian) + momentum_spectrum = eigenspectrum(jw_momentum) + position_spectrum = eigenspectrum(jw_position) + + # Confirm momentum spectrum is shifted 2.8372 / length_scale higher. + max_difference = numpy.amax(momentum_spectrum - position_spectrum) + min_difference = numpy.amax(momentum_spectrum - position_spectrum) + self.assertAlmostEqual(max_difference, 2.8372 / length_scale) + self.assertAlmostEqual(min_difference, 2.8372 / length_scale) + def test_coefficients(self): # Test that the coefficients post-JW transform are as claimed in paper. @@ -179,11 +208,11 @@ def test_coefficients(self): volume = grid.volume_scale() # Kinetic operator. - kinetic = position_kinetic_operator(grid, spinless) + kinetic = dual_basis_kinetic(grid, spinless) qubit_kinetic = jordan_wigner(kinetic) # Potential operator. - potential = position_potential_operator(grid, spinless) + potential = dual_basis_potential(grid, spinless) qubit_potential = jordan_wigner(potential) # Check identity. @@ -259,8 +288,6 @@ def test_coefficients(self): zpzq = ((min(p, q), 'Z'), (max(p, q), 'Z')) if zpzq in qubit_potential.terms: potential_coefficient = qubit_potential.terms[zpzq] - else: - potential_coefficient = 0. for indices_c in grid.all_points_indices(): momenta = momentum_vector(indices_c, grid) @@ -275,17 +302,19 @@ def test_coefficients(self): self.assertAlmostEqual( potential_coefficient, paper_potential_coefficient) - def test_jordan_wigner_position_jellium(self): + def test_jordan_wigner_dual_basis_jellium(self): # Parameters. grid = Grid(dimensions=2, length=3, scale=1.) spinless = True - # Compute fermionic Hamiltonian. - fermion_hamiltonian = jellium_model(grid, spinless, False) + # Compute fermionic Hamiltonian. Include then subtract constant. + fermion_hamiltonian = dual_basis_jellium_model( + grid, spinless, include_constant=True) qubit_hamiltonian = jordan_wigner(fermion_hamiltonian) + qubit_hamiltonian -= QubitOperator((), 2.8372) # Compute Jordan-Wigner Hamiltonian. - test_hamiltonian = jordan_wigner_position_jellium(grid, spinless) + test_hamiltonian = jordan_wigner_dual_basis_jellium(grid, spinless) # Make sure Hamiltonians are the same. self.assertTrue(test_hamiltonian.isclose(qubit_hamiltonian)) @@ -294,14 +323,22 @@ def test_jordan_wigner_position_jellium(self): n_qubits = count_qubits(qubit_hamiltonian) if spinless: paper_n_terms = 1 - .5 * n_qubits + 1.5 * (n_qubits ** 2) - else: - paper_n_terms = 1 - .5 * n_qubits + n_qubits ** 2 num_nonzeros = sum(1 for coeff in qubit_hamiltonian.terms.values() if coeff != 0.0) self.assertTrue(num_nonzeros <= paper_n_terms) + def test_jordan_wigner_dual_basis_jellium_constant_shift(self): + length_scale = 0.6 + grid = Grid(dimensions=2, length=3, scale=length_scale) + spinless = True + + hamiltonian_without_constant = jordan_wigner_dual_basis_jellium( + grid, spinless, include_constant=False) + hamiltonian_with_constant = jordan_wigner_dual_basis_jellium( + grid, spinless, include_constant=True) + + difference = hamiltonian_with_constant - hamiltonian_without_constant + expected = FermionOperator.identity() * (2.8372 / length_scale) -# Run test. -if __name__ == '__main__': - unittest.main() + self.assertTrue(expected.isclose(difference)) diff --git a/src/fermilib/utils/_molecular_data.py b/src/fermilib/utils/_molecular_data.py index e615730..886d73e 100644 --- a/src/fermilib/utils/_molecular_data.py +++ b/src/fermilib/utils/_molecular_data.py @@ -16,7 +16,7 @@ import h5py import numpy import os -import sys +import uuid from fermilib.config import * from fermilib.ops import InteractionOperator, InteractionRDM @@ -43,9 +43,10 @@ class MissingCalculationError(Exception): pass -# Functions to change from Bohr to Angstroms and back. +# Functions to change from Bohr to angstroms and back. def bohr_to_angstroms(distance): - return 0.529177 * distance + # Value defined so it is the inverse to numerical precision of angs to bohr + return 0.5291772458017723 * distance def angstroms_to_bohr(distance): @@ -98,7 +99,7 @@ def name_molecule(geometry, Args: geometry: A list of tuples giving the coordinates of each atom. example is [('H', (0, 0, 0)), ('H', (0, 0, 0.7414))]. - Distances in atomic units. Use atomic symbols to specify atoms. + Distances in angstrom. Use atomic symbols to specify atoms. basis: A string giving the basis set. An example is 'cc-pvtz'. multiplicity: An integer giving the spin multiplicity. charge: An integer giving the total molecular charge. @@ -145,9 +146,9 @@ def name_molecule(geometry, # Add charge. if charge > 0: - name += '{}+'.format(charge) + name += '_{}+'.format(charge) elif charge < 0: - name += '{}-'.format(charge) + name += '_{}-'.format(charge) # Optionally add descriptive tag and return. if description: @@ -167,7 +168,7 @@ def geometry_from_file(file_name): Returns: geometry: A list of tuples giving the coordinates of each atom. example is [('H', (0, 0, 0)), ('H', (0, 0, 0.7414))]. - Distances in atomic units. Use atomic symbols to specify atoms. + Distances in angstrom. Use atomic symbols to specify atoms. """ geometry = [] with open(file_name, 'r') as stream: @@ -211,15 +212,18 @@ class MolecularData(object): orbital_energies: Numpy array giving the canonical orbital energies. fock_matrix: Numpy array giving the Fock matrix. orbital_overlaps: Numpy array giving the orbital overlap coefficients. - kinetic_integrals: Numpy array giving 1-body kinetic energy integrals. - potential_integrals: Numpy array giving 1-body potential integrals. + one_body_integrals: Numpy array of one-electron integrals + two_body_integrals: Numpy array of two-electron integrals mp2_energy: Energy from MP2 perturbation theory. cisd_energy: Energy from configuration interaction singles + doubles. cisd_one_rdm: Numpy array giving 1-RDM from CISD calculation. + cisd_two_rdm: Numpy array giving 2-RDM from CISD calculation. fci_energy: Exact energy of molecule within given basis. fci_one_rdm: Numpy array giving 1-RDM from FCI calculation. + fci_two_rdm: Numpy array giving 2-RDM from FCI calculation. ccsd_energy: Energy from coupled cluster singles + doubles. - ccsd_amplitudes: Molecular operator holding coupled cluster amplitude. + ccsd_single_amps: Numpy array holding single amplitudes + ccsd_double_amps: Numpy array holding double amplitudes """ def __init__(self, geometry=None, basis=None, multiplicity=None, charge=0, description="", filename="", data_directory=None): @@ -228,7 +232,7 @@ def __init__(self, geometry=None, basis=None, multiplicity=None, Args: geometry: A list of tuples giving the coordinates of each atom. An example is [('H', (0, 0, 0)), ('H', (0, 0, 0.7414))]. - Distances in atomic units. Use atomic symbols to + Distances in angstrom. Use atomic symbols to specify atoms. Only optional if loading from file. basis: A string giving the basis set. An example is 'cc-pvtz'. Only optional if loading from file. @@ -254,6 +258,7 @@ def __init__(self, geometry=None, basis=None, multiplicity=None, else: self.filename = filename self.load() + self.init_lazy_properties() return else: raise ValueError("Geometry, basis, multiplicity must be" @@ -298,132 +303,305 @@ def __init__(self, geometry=None, basis=None, multiplicity=None, # Attributes generated from SCF calculation. self.hf_energy = None - self.canonical_orbitals = None self.orbital_energies = None # Attributes generated from integrals. - self.orbital_overlaps = None - self.one_body_integrals = None - self.two_body_integrals = None + self._orbital_overlaps = None # Attributes generated from MP2 calculation. self.mp2_energy = None # Attributes generated from CISD calculation. self.cisd_energy = None - self.cisd_one_rdm = None - self.cisd_two_rdm = None # Attributes generated from exact diagonalization. self.fci_energy = None - self.fci_one_rdm = None - self.fci_two_rdm = None # Attributes generated from CCSD calculation. self.ccsd_energy = None - self.ccsd_amplitudes = None + + # Initialize attributes that will be loaded only upon demand + self.init_lazy_properties() + + def init_lazy_properties(self): + """Initializes properties loaded on demand to None""" + + # Molecular orbitals + self._canonical_orbitals = None + + # Electronic Integrals + self._one_body_integrals = None + self._two_body_integrals = None + + # CI RDMs + self._cisd_one_rdm = None + self._cisd_two_rdm = None + + # FCI RDMs + self._fci_one_rdm = None + self._fci_two_rdm = None + + # Coupled cluster amplitudes + self._ccsd_single_amps = None + self._ccsd_double_amps = None + + @property + def canonical_orbitals(self): + if self._canonical_orbitals is None: + data = self.get_from_file("canonical_orbitals") + self._canonical_orbitals = (data if data is not None and + data.dtype.num != 0 else None) + return self._canonical_orbitals + + @canonical_orbitals.setter + def canonical_orbitals(self, value): + self._canonical_orbitals = value + + @property + def orbital_overlaps(self): + if self._orbital_overlaps is None: + data = self.get_from_file("one_body_integrals") + self._orbital_overlaps = (data if data is not None and + data.dtype.num != 0 else None) + return self._orbital_overlaps + + @orbital_overlaps.setter + def orbital_overlaps(self, value): + self._orbital_overlaps = value + + @property + def one_body_integrals(self): + if self._one_body_integrals is None: + data = self.get_from_file("one_body_integrals") + self._one_body_integrals = (data if data is not None and + data.dtype.num != 0 else None) + return self._one_body_integrals + + @one_body_integrals.setter + def one_body_integrals(self, value): + self._one_body_integrals = value + + @property + def two_body_integrals(self): + if self._two_body_integrals is None: + data = self.get_from_file("two_body_integrals") + self._two_body_integrals = (data if data is not None and + data.dtype.num != 0 else None) + return self._two_body_integrals + + @two_body_integrals.setter + def two_body_integrals(self, value): + self._two_body_integrals = value + + @property + def cisd_one_rdm(self): + if self._cisd_one_rdm is None: + data = self.get_from_file("cisd_one_rdm") + self._cisd_one_rdm = (data if data is not None and + data.dtype.num != 0 else None) + return self._cisd_one_rdm + + @cisd_one_rdm.setter + def cisd_one_rdm(self, value): + self._cisd_one_rdm = value + + @property + def cisd_two_rdm(self): + if self._cisd_two_rdm is None: + data = self.get_from_file("cisd_two_rdm") + self._cisd_two_rdm = (data if data is not None and + data.dtype.num != 0 else None) + return self._cisd_two_rdm + + @cisd_two_rdm.setter + def cisd_two_rdm(self, value): + self._cisd_two_rdm = value + + @property + def fci_one_rdm(self): + if self._fci_one_rdm is None: + data = self.get_from_file("fci_one_rdm") + self._fci_one_rdm = (data if data is not None and + data.dtype.num != 0 else None) + return self._fci_one_rdm + + @fci_one_rdm.setter + def fci_one_rdm(self, value): + self._fci_one_rdm = value + + @property + def fci_two_rdm(self): + if self._fci_two_rdm is None: + data = self.get_from_file("fci_two_rdm") + self._fci_two_rdm = (data if data is not None and + data.dtype.num != 0 else None) + return self._fci_two_rdm + + @fci_two_rdm.setter + def fci_two_rdm(self, value): + self._fci_two_rdm = value + + @property + def ccsd_single_amps(self): + if self._ccsd_single_amps is None: + data = self.get_from_file("ccsd_single_amps") + self._ccsd_single_amps = (data if data is not None and + data.dtype.num != 0 else None) + return self._ccsd_single_amps + + @ccsd_single_amps.setter + def ccsd_single_amps(self, value): + self._ccsd_single_amps = value + + @property + def ccsd_double_amps(self): + if self._ccsd_double_amps is None: + data = self.get_from_file("ccsd_double_amps") + self._ccsd_double_amps = (data if data is not None and + data.dtype.num != 0 else None) + return self._ccsd_double_amps + + @ccsd_double_amps.setter + def ccsd_double_amps(self, value): + self._ccsd_double_amps = value def save(self): """Method to save the class under a systematic name.""" - - # Load two body integrals/rdms from file before re-saving, since - # they aren't loaded by default - if (os.path.isfile("{}.hdf5".format(self.filename))): - if (self.one_body_integrals is not None and - self.two_body_integrals is None): - self.one_body_integrals, self.two_body_integrals = ( - self.get_integrals()) - if self.cisd_one_rdm is not None and self.cisd_two_rdm is None: - rdm = self.get_molecular_rdm() - self.cisd_two_rdm = rdm.two_body_tensor - if self.fci_one_rdm is not None and self.fci_two_rdm is None: - rdm = self.get_molecular_rdm(use_fci=True) - self.fci_two_rdm = rdm.two_body_tensor - - with h5py.File("{}.hdf5".format(self.filename), "w") as f: + # Create a temporary file and swap it to the original name in case + # data needs to be loaded while saving + tmp_name = uuid.uuid4() + with h5py.File("{}.hdf5".format(tmp_name), "w") as f: # Save geometry (atoms and positions need to be separate): d_geom = f.create_group("geometry") atoms = [numpy.string_(item[0]) for item in self.geometry] positions = numpy.array([list(item[1]) for item in self.geometry]) - d_geom["atoms"] = atoms - d_geom["positions"] = positions + d_geom.create_dataset("atoms", data=atoms) + d_geom.create_dataset("positions", data=positions) # Save basis: - f["basis"] = numpy.string_(self.basis) + f.create_dataset("basis", data=numpy.string_(self.basis)) # Save multiplicity: - f["multiplicity"] = self.multiplicity + f.create_dataset("multiplicity", data=self.multiplicity) # Save charge: - f["charge"] = self.charge + f.create_dataset("charge", data=self.charge) # Save description: - f["description"] = numpy.string_(self.description) + f.create_dataset("description", + data=numpy.string_(self.description)) # Save name: - f["name"] = numpy.string_(self.name) + f.create_dataset("name", data=numpy.string_(self.name)) # Save n_atoms: - f["n_atoms"] = self.n_atoms + f.create_dataset("n_atoms", data=self.n_atoms) # Save atoms: - f["atoms"] = numpy.string_(self.atoms) + f.create_dataset("atoms", data=numpy.string_(self.atoms)) # Save protons: - f["protons"] = self.protons + f.create_dataset("protons", data=self.protons) # Save n_electrons: - f["n_electrons"] = self.n_electrons + f.create_dataset("n_electrons", data=self.n_electrons) # Save generic attributes from calculations: - f["n_orbitals"] = (self.n_orbitals if self.n_orbitals is - not None else False) - f["n_qubits"] = (self.n_qubits if self.n_qubits is - not None else False) - f["nuclear_repulsion"] = (self.nuclear_repulsion if - self.nuclear_repulsion is - not None else False) + f.create_dataset("n_orbitals", + data=(self.n_orbitals if self.n_orbitals + is not None else False)) + f.create_dataset("n_qubits", + data=(self.n_qubits if + self.n_qubits is not None else False)) + f.create_dataset("nuclear_repulsion", + data=(self.nuclear_repulsion if + self.nuclear_repulsion is not None else + False)) # Save attributes generated from SCF calculation. - f["hf_energy"] = (self.hf_energy if - self.hf_energy is not None else False) - f["canonical_orbitals"] = (self.canonical_orbitals if - self.canonical_orbitals is - not None else False) - f["orbital_energies"] = (self.orbital_energies if - self.orbital_energies is - not None else False) + f.create_dataset("hf_energy", data=(self.hf_energy if + self.hf_energy is not None + else False)) + f.create_dataset("canonical_orbitals", + data=(self.canonical_orbitals if + self.canonical_orbitals is + not None else False), + compression=("gzip" if self.canonical_orbitals + is not None else None)) + f.create_dataset("orbital_energies", + data=(self.orbital_energies if + self.orbital_energies is not None else + False)) # Save attributes generated from integrals. - f["orbital_overlaps"] = (self.orbital_overlaps if - self.orbital_overlaps is - not None else False) - f["one_body_integrals"] = (self.one_body_integrals if - self.one_body_integrals is - not None else False) - f["two_body_integrals"] = (self.two_body_integrals if - self.two_body_integrals is - not None else False) + f.create_dataset("orbital_overlaps", + data=(self.orbital_overlaps if + self.orbital_overlaps is + not None else False), + compression=("gzip" if self.orbital_overlaps + is not None else None)) + f.create_dataset("one_body_integrals", + data=(self.one_body_integrals if + self.one_body_integrals is + not None else False), + compression=("gzip" if self.one_body_integrals + is not None else None)) + f.create_dataset("two_body_integrals", + data=(self.two_body_integrals if + self.two_body_integrals is + not None else False), + compression=("gzip" if self.two_body_integrals + is not None else None)) # Save attributes generated from MP2 calculation. - f["mp2_energy"] = (self.mp2_energy if - self.mp2_energy is not None else False) + f.create_dataset("mp2_energy", + data=(self.mp2_energy if + self.mp2_energy is not None else False)) # Save attributes generated from CISD calculation. - f["cisd_energy"] = (self.cisd_energy if - self.cisd_energy is not None else False) - f["cisd_one_rdm"] = (self.cisd_one_rdm if - self.cisd_one_rdm is not None else False) - f["cisd_two_rdm"] = (self.cisd_two_rdm if - self.cisd_two_rdm is not None else False) + f.create_dataset("cisd_energy", + data=(self.cisd_energy if + self.cisd_energy is not None else False)) + f.create_dataset("cisd_one_rdm", + data=(self.cisd_one_rdm if + self.cisd_one_rdm is not None else False), + compression=("gzip" if self.cisd_one_rdm + is not None else None)) + f.create_dataset("cisd_two_rdm", + data=(self.cisd_two_rdm if + self.cisd_two_rdm is not None else False), + compression=("gzip" if self.cisd_two_rdm + is not None else None)) # Save attributes generated from exact diagonalization. - f["fci_energy"] = (self.fci_energy if - self.fci_energy is not None else False) - f["fci_one_rdm"] = (self.fci_one_rdm if - self.fci_one_rdm is not None else False) - f["fci_two_rdm"] = (self.fci_two_rdm if - self.fci_two_rdm is not None else False) + f.create_dataset("fci_energy", + data=(self.fci_energy if + self.fci_energy is not None else False)) + f.create_dataset("fci_one_rdm", + data=(self.fci_one_rdm if + self.fci_one_rdm is not None else False), + compression=("gzip" if self.fci_one_rdm + is not None else None)) + f.create_dataset("fci_two_rdm", + data=(self.fci_two_rdm if + self.fci_two_rdm is not None else False), + compression=("gzip" if self.fci_two_rdm is not + None else None)) # Save attributes generated from CCSD calculation. - f["ccsd_energy"] = (self.ccsd_energy if - self.ccsd_energy is not None else False) - ccsd_a = f.create_group("ccsd_amplitudes") - ccsd_a["constant"] = (self.ccsd_amplitudes.constant if - self.ccsd_amplitudes is not None else False) - ccsd_a["one_body_tensor"] = (self.ccsd_amplitudes.one_body_tensor - if self.ccsd_amplitudes is - not None else False) - ccsd_a["two_body_tensor"] = (self.ccsd_amplitudes.two_body_tensor - if self.ccsd_amplitudes is - not None else False) + f.create_dataset("ccsd_energy", + data=(self.ccsd_energy if + self.ccsd_energy is not None else False)) + f.create_dataset("ccsd_single_amps", + data=(self.ccsd_single_amps + if self.ccsd_single_amps is not None else + False), + compression=("gzip" if self.ccsd_single_amps + is not None else None)) + f.create_dataset("ccsd_double_amps", + data=(self.ccsd_double_amps + if self.ccsd_double_amps is + not None else False), + compression=("gzip" if self.ccsd_double_amps + is not None else None)) + # Remove old file first for compatibility with systems that don't allow + # rename replacement. Catching OSError for when file does not exist + # yet + try: + os.remove("{}.hdf5".format(self.filename)) + except OSError: + pass + + os.rename("{}.hdf5".format(tmp_name), + "{}.hdf5".format(self.filename)) def load(self): geometry = [] + with h5py.File("{}.hdf5".format(self.filename), "r") as f: # Load geometry: for atom, pos in zip(f["geometry/atoms"][...], @@ -449,47 +627,50 @@ def load(self): # Load n_electrons: self.n_electrons = int(f["n_electrons"][...]) # Load generic attributes from calculations: - d_0 = f["n_orbitals"][...] - self.n_orbitals = int(d_0) if d_0.dtype.num != 0 else None - d_1 = f["n_qubits"][...] - self.n_qubits = int(d_1) if d_1.dtype.num != 0 else None - d_2 = f["nuclear_repulsion"][...] - self.nuclear_repulsion = float(d_2) if d_2.dtype.num != 0 else None + data = f["n_orbitals"][...] + self.n_orbitals = int(data) if data.dtype.num != 0 else None + data = f["n_qubits"][...] + self.n_qubits = int(data) if data.dtype.num != 0 else None + data = f["nuclear_repulsion"][...] + self.nuclear_repulsion = (float(data) if data.dtype.num != 0 else + None) # Load attributes generated from SCF calculation. - d_3 = f["hf_energy"][...] - self.hf_energy = d_3 if d_3.dtype.num != 0 else None - d_4 = f["canonical_orbitals"][...] - self.canonical_orbitals = d_4 if d_4.dtype.num != 0 else None - d_5 = f["orbital_energies"][...] - self.orbital_energies = d_5 if d_5.dtype.num != 0 else None - # Load attributes generated from integrals. - d_6 = f["orbital_overlaps"][...] - self.orbital_overlaps = d_6 if d_6.dtype.num != 0 else None - d_7 = f["one_body_integrals"][...] - self.one_body_integrals = d_7 if d_7.dtype.num != 0 else None + data = f["hf_energy"][...] + self.hf_energy = data if data.dtype.num != 0 else None + data = f["orbital_energies"][...] + self.orbital_energies = data if data.dtype.num != 0 else None # Load attributes generated from MP2 calculation. - d_8 = f["mp2_energy"][...] - self.mp2_energy = d_8 if d_8.dtype.num != 0 else None + data = f["mp2_energy"][...] + self.mp2_energy = data if data.dtype.num != 0 else None # Load attributes generated from CISD calculation. - d_9 = f["cisd_energy"][...] - self.cisd_energy = d_9 if d_9.dtype.num != 0 else None - d_10 = f["cisd_one_rdm"][...] - self.cisd_one_rdm = d_10 if d_10.dtype.num != 0 else None + data = f["cisd_energy"][...] + self.cisd_energy = data if data.dtype.num != 0 else None # Load attributes generated from exact diagonalization. - d_11 = f["fci_energy"][...] - self.fci_energy = d_11 if d_11.dtype.num != 0 else None - d_12 = f["fci_one_rdm"][...] - self.fci_one_rdm = d_12 if d_12.dtype.num != 0 else None + data = f["fci_energy"][...] + self.fci_energy = data if data.dtype.num != 0 else None # Load attributes generated from CCSD calculation. - d_13 = f["ccsd_energy"][...] - self.ccsd_energy = d_13 if d_13.dtype.num != 0 else None - if f["ccsd_amplitudes/constant"][...].dtype.num != 0: - constant = f["ccsd_amplitudes/constant"][...] - one = f["ccsd_amplitudes/one_body_tensor"][...] - two = f["ccsd_amplitudes/two_body_tensor"][...] - self.ccsd_amplitudes = InteractionOperator(constant, one, two) - else: - self.ccsd_amplitudes = None + data = f["ccsd_energy"][...] + self.ccsd_energy = data if data.dtype.num != 0 else None + + def get_from_file(self, property_name): + """Helper routine to re-open HDF5 file and pull out single property + + Args: + property_name(string): Property name to load from self.filename + + Returns: + The data located at file[property_name] for the HDF5 file at + self.filename. Returns None if the key is not found in the + file. + """ + try: + with h5py.File("{}.hdf5".format(self.filename), "r") as f: + data = f[property_name][...] + except KeyError: + data = None + except IOError: + data = None + return data def get_n_alpha_electrons(self): """Return number of alpha electrons.""" @@ -516,11 +697,6 @@ def get_integrals(self): raise MissingCalculationError( 'Missing SCF in {}, run before loading integrals.'.format( self.filename)) - - # Get integrals and return. - with h5py.File("{}.hdf5".format(self.filename), "r") as f: - tmp = f["two_body_integrals"][...] - self.two_body_integrals = tmp if tmp.dtype.num != 0 else None return self.one_body_integrals, self.two_body_integrals def get_active_space_integrals(self, @@ -680,20 +856,16 @@ def get_molecular_rdm(self, use_fci=False): 'Missing FCI RDM in {}'.format(self.filename) + 'Run FCI calculation before loading FCI RDMs.') else: - rdm_name = "fci_two_rdm" one_rdm = self.fci_one_rdm + two_rdm = self.fci_two_rdm else: if self.cisd_energy is None: raise MissingCalculationError( 'Missing CISD RDM in {}'.format(self.filename) + 'Run CISD calculation before loading CISD RDMs.') else: - rdm_name = "cisd_two_rdm" one_rdm = self.cisd_one_rdm - - with h5py.File("{}.hdf5".format(self.filename), "r") as f: - tmp = f[rdm_name][...] - two_rdm = self.two_rdm = tmp if tmp.dtype.num != 0 else None + two_rdm = self.cisd_two_rdm # Truncate. one_rdm[numpy.absolute(one_rdm) < EQ_TOLERANCE] = 0. diff --git a/src/fermilib/utils/_molecular_data_test.py b/src/fermilib/utils/_molecular_data_test.py index 787cd31..ff88772 100644 --- a/src/fermilib/utils/_molecular_data_test.py +++ b/src/fermilib/utils/_molecular_data_test.py @@ -28,12 +28,21 @@ def setUp(self): self.geometry = [('H', (0., 0., 0.)), ('H', (0., 0., 0.7414))] self.basis = 'sto-3g' self.multiplicity = 1 - filename = os.path.join(THIS_DIRECTORY, 'data', - 'H2_sto-3g_singlet_0.7414') + self.filename = os.path.join(THIS_DIRECTORY, 'data', + 'H2_sto-3g_singlet_0.7414') self.molecule = MolecularData( - self.geometry, self.basis, self.multiplicity, filename=filename) + self.geometry, self.basis, self.multiplicity, + filename=self.filename) self.molecule.load() + def testUnitConversion(self): + """Test the unit conversion routines""" + unit_angstrom = 1.0 + bohr = angstroms_to_bohr(unit_angstrom) + self.assertAlmostEqual(bohr, 1.889726) + inverse_transform = bohr_to_angstroms(bohr) + self.assertAlmostEqual(inverse_transform, 1.0) + def test_name_molecule(self): charge = 0 correct_name = str('H2_sto-3g_singlet_0.7414') @@ -45,6 +54,24 @@ def test_name_molecule(self): self.assertEqual(correct_name, computed_name) self.assertEqual(correct_name, self.molecule.name) + # Check (+) charge + charge = 1 + correct_name = "H2_sto-3g_singlet_1+_0.7414" + computed_name = name_molecule(self.geometry, + self.basis, + self.multiplicity, + charge, + description="0.7414") + self.assertEqual(correct_name, computed_name) + + def test_invalid_multiplicity(self): + geometry = [('H', (0., 0., 0.)), ('H', (0., 0., 0.7414))] + basis = 'sto-3g' + multiplicity = -1 + with self.assertRaises(MoleculeNameError): + molecule = MolecularData( + geometry, basis, multiplicity) + def test_geometry_from_file(self): water_geometry = [('O', (0., 0., 0.)), ('H', (0.757, 0.586, 0.)), @@ -60,10 +87,16 @@ def test_geometry_from_file(self): def test_save_load(self): n_atoms = self.molecule.n_atoms + orbitals = self.molecule.canonical_orbitals + self.assertFalse(orbitals is None) + overlaps = self.molecule.orbital_overlaps + self.assertFalse(overlaps is None) self.molecule.n_atoms += 1 self.assertEqual(self.molecule.n_atoms, n_atoms + 1) self.molecule.load() self.assertEqual(self.molecule.n_atoms, n_atoms) + dummy_data = self.molecule.get_from_file("dummy_entry") + self.assertTrue(dummy_data is None) def test_dummy_save(self): @@ -81,7 +114,7 @@ def test_dummy_save(self): molecule.n_orbitals = 10 molecule.n_qubits = 10 molecule.nuclear_repulsion = -12.3 - molecule.hf_energy = 88. + molecule.hf_energy = 99. molecule.canonical_orbitals = [1, 2, 3, 4] molecule.orbital_energies = [5, 6, 7, 8] molecule.orbital_overlaps = [1, 2, 3, 4] @@ -95,6 +128,27 @@ def test_dummy_save(self): molecule.fci_one_rdm = numpy.arange(11) molecule.fci_two_rdm = numpy.arange(11) molecule.ccsd_energy = 88. + molecule.ccsd_single_amps = [1, 2, 3] + molecule.ccsd_double_amps = [1, 2, 3] + + # Test missing calculation and information exceptions + molecule.hf_energy = None + with self.assertRaises(MissingCalculationError): + one_body_ints, two_body_ints = molecule.get_integrals() + molecule.hf_energy = 99. + + with self.assertRaises(ValueError): + molecule.get_active_space_integrals([], []) + + molecule.fci_energy = None + with self.assertRaises(MissingCalculationError): + molecule.get_molecular_rdm(use_fci=True) + molecule.fci_energy = 232. + + molecule.cisd_energy = None + with self.assertRaises(MissingCalculationError): + molecule.get_molecular_rdm(use_fci=False) + molecule.cisd_energy = 232. # Save molecule. molecule.save() @@ -117,6 +171,26 @@ def test_dummy_save(self): finally: os.remove(filename + '.hdf5') + def test_file_loads(self): + """Test different filename specs""" + data_directory = os.path.join(THIS_DIRECTORY, 'data') + molecule = MolecularData( + self.geometry, self.basis, self.multiplicity, + filename=self.filename) + test_hf_energy = molecule.hf_energy + molecule = MolecularData( + self.geometry, self.basis, self.multiplicity, + filename=self.filename + ".hdf5", + data_directory=data_directory) + self.assertAlmostEqual(test_hf_energy, molecule.hf_energy) + + molecule = MolecularData(filename=self.filename + ".hdf5") + integrals = molecule.one_body_integrals + self.assertTrue(integrals is not None) + + with self.assertRaises(ValueError): + MolecularData() + def test_active_space(self): """Test simple active space truncation features""" diff --git a/src/fermilib/utils/_operator_utils.py b/src/fermilib/utils/_operator_utils.py index b02f81c..cd3f77f 100644 --- a/src/fermilib/utils/_operator_utils.py +++ b/src/fermilib/utils/_operator_utils.py @@ -13,13 +13,22 @@ """This module provides generic tools for classes in ops/""" from __future__ import absolute_import +import marshal import numpy +import os +import time +from fermilib.config import * from fermilib.ops import * +from future.builtins.iterators import map, zip from projectq.ops import QubitOperator +class OperatorUtilsError(Exception): + pass + + def eigenspectrum(operator): """Compute the eigenspectrum of an operator. @@ -97,8 +106,116 @@ def is_identity(operator): def commutator(operator_a, operator_b): - """Compute the commutator of two QubitOperators or FermionOperators.""" - if (isinstance(operator_a, (QubitOperator, FermionOperator)) and - isinstance(operator_b, (QubitOperator, FermionOperator))): - return operator_a * operator_b - operator_b * operator_a - raise TypeError('Operator of invalid type.') + """Compute the commutator of two QubitOperators or FermionOperators. + + Args: + operator_a, operator_b (FermionOperator): Operators in commutator. + """ + if not ((isinstance(operator_a, FermionOperator) and + isinstance(operator_b, FermionOperator)) or + (isinstance(operator_a, QubitOperator) and + isinstance(operator_b, QubitOperator))): + raise TypeError('operator_a and operator_b must both be Fermion or' + ' QubitOperators.') + result = operator_a * operator_b + result -= operator_b * operator_a + return result + + +def save_operator(operator, file_name=None, data_directory=None): + """Save FermionOperator or QubitOperator to file. + + Args: + operator: An instance of FermionOperator or QubitOperator. + file_name: The name of the saved file. + data_directory: Optional data directory to change from default data + directory specified in config file. + + Raises: + OperatorUtilsError: Not saved, file already exists. + TypeError: Operator of invalid type. + """ + file_path = get_file_path(file_name, data_directory) + + if os.path.isfile(file_path): + raise OperatorUtilsError("Not saved, file already exists.") + + if isinstance(operator, FermionOperator): + operator_type = "FermionOperator" + elif isinstance(operator, QubitOperator): + operator_type = "QubitOperator" + elif (isinstance(operator, InteractionOperator) or + isinstance(operator, InteractionRDM)): + raise NotImplementedError('Not yet implemented for InteractionOperator' + ' or InteractionRDM.') + else: + raise TypeError('Operator of invalid type.') + + tm = operator.terms + with open(file_path, 'wb') as f: + marshal.dump((operator_type, dict(zip(tm.keys(), + map(complex, tm.values())))), f) + f.close() + + +def load_operator(file_name=None, data_directory=None): + """Load FermionOperator or QubitOperator from file. + + Args: + file_name: The name of the saved file. + data_directory: Optional data directory to change from default data + directory specified in config file. + + Returns: + operator: The stored FermionOperator or QubitOperator + + Raises: + TypeError: Operator of invalid type. + """ + file_path = get_file_path(file_name, data_directory) + + with open(file_path, 'rb') as f: + data = marshal.load(f) + operator_type = data[0] + operator_terms = data[1] + + if operator_type == 'FermionOperator': + operator = FermionOperator() + for term in operator_terms: + operator += FermionOperator(term, operator_terms[term]) + elif operator_type == 'QubitOperator': + operator = QubitOperator() + for term in operator_terms: + operator += QubitOperator(term, operator_terms[term]) + else: + raise TypeError('Operator of invalid type.') + + return operator + + +def get_file_path(file_name, data_directory): + """Compute file_path for the file that stores operator. + + Args: + file_name: The name of the saved file. + data_directory: Optional data directory to change from default data + directory specified in config file. + + Returns: + file_path (string): File path. + + Raises: + OperatorUtilsError: File name is not provided. + """ + if file_name: + if file_name[-5:] != '.data': + file_name = file_name + ".data" + else: + raise OperatorUtilsError("File name is not provided.") + + if data_directory is None: + file_path = DATA_DIRECTORY + '/' + file_name + else: + file_path = data_directory + '/' + file_name + + return file_path diff --git a/src/fermilib/utils/_operator_utils_test.py b/src/fermilib/utils/_operator_utils_test.py index d205504..2db46aa 100644 --- a/src/fermilib/utils/_operator_utils_test.py +++ b/src/fermilib/utils/_operator_utils_test.py @@ -14,12 +14,18 @@ from __future__ import absolute_import import numpy +import os import unittest +from fermilib.config import * from fermilib.ops import * from fermilib.transforms import jordan_wigner, get_interaction_operator from fermilib.utils import (eigenspectrum, commutator, count_qubits, is_identity) +from fermilib.utils._operator_utils import (commutator, count_qubits, + eigenspectrum, get_file_path, + is_identity, load_operator, + OperatorUtilsError, save_operator) from projectq.ops import QubitOperator @@ -35,16 +41,26 @@ def setUp(self): self.interaction_operator = get_interaction_operator( self.fermion_operator) - def test_n_qubits(self): + def test_n_qubits_single_fermion_term(self): self.assertEqual(self.n_qubits, count_qubits(self.fermion_term)) + + def test_n_qubits_fermion_operator(self): self.assertEqual(self.n_qubits, count_qubits(self.fermion_operator)) + + def test_n_qubits_qubit_operator(self): self.assertEqual(self.n_qubits, count_qubits(self.qubit_operator)) + + def test_n_qubits_interaction_operator(self): self.assertEqual(self.n_qubits, count_qubits(self.interaction_operator)) + def test_n_qubits_bad_type(self): + with self.assertRaises(TypeError): + count_qubits('twelve') + def test_eigenspectrum(self): fermion_eigenspectrum = eigenspectrum(self.fermion_operator) qubit_eigenspectrum = eigenspectrum(self.qubit_operator) @@ -55,16 +71,130 @@ def test_eigenspectrum(self): self.assertAlmostEqual(fermion_eigenspectrum[i], interaction_eigenspectrum[i]) - def test_is_identity(self): + def test_is_identity_unit_fermionoperator(self): self.assertTrue(is_identity(FermionOperator(()))) + + def test_is_identity_double_of_unit_fermionoperator(self): self.assertTrue(is_identity(2. * FermionOperator(()))) + + def test_is_identity_unit_qubitoperator(self): self.assertTrue(is_identity(QubitOperator(()))) + + def test_is_identity_double_of_unit_qubitoperator(self): self.assertTrue(is_identity(QubitOperator((), 2.))) + + def test_not_is_identity_single_term_fermionoperator(self): self.assertFalse(is_identity(FermionOperator('1^'))) + + def test_not_is_identity_single_term_qubitoperator(self): self.assertFalse(is_identity(QubitOperator('X1'))) + + def test_not_is_identity_zero_fermionoperator(self): self.assertFalse(is_identity(FermionOperator())) + + def test_not_is_identity_zero_qubitoperator(self): self.assertFalse(is_identity(QubitOperator())) + def test_is_identity_bad_type(self): + with self.assertRaises(TypeError): + is_identity('eleven') + + +class SaveLoadOperatorTest(unittest.TestCase): + def setUp(self): + self.n_qubits = 5 + self.fermion_term = FermionOperator('1^ 2^ 3 4', -3.17) + self.fermion_operator = self.fermion_term + hermitian_conjugated( + self.fermion_term) + self.qubit_operator = jordan_wigner(self.fermion_operator) + + def test_save_and_load_operators(self): + file_name = "test_file" + + save_operator(self.fermion_operator, file_name) + loaded_fermion_operator = load_operator(file_name) + self.assertEqual(self.fermion_operator.terms, + loaded_fermion_operator.terms) + os.remove(get_file_path(file_name, DATA_DIRECTORY)) + + save_operator(self.qubit_operator, file_name) + loaded_qubit_operator = load_operator(file_name) + self.assertEqual(self.qubit_operator.terms, + loaded_qubit_operator.terms) + os.remove(get_file_path(file_name, DATA_DIRECTORY)) + + def test_save_and_load_operators_errors(self): + file_name = "test_file" + + with self.assertRaises(OperatorUtilsError): + save_operator("invalid_operator_type") + with self.assertRaises(OperatorUtilsError): + save_operator(self.fermion_operator) + + save_operator(self.fermion_operator, file_name) + with self.assertRaises(OperatorUtilsError): + save_operator(self.fermion_operator, file_name) + os.remove(get_file_path(file_name, DATA_DIRECTORY)) + + constant = 100.0 + one_body = numpy.zeros((self.n_qubits, self.n_qubits), float) + two_body = numpy.zeros((self.n_qubits, self.n_qubits, + self.n_qubits, self.n_qubits), float) + one_body[1, 1] = 10.0 + two_body[1, 2, 3, 4] = 12.0 + interaction_operator = InteractionOperator( + constant, one_body, two_body) + with self.assertRaises(NotImplementedError): + save_operator(interaction_operator, file_name) + + def test_save_bad_type(self): + with self.assertRaises(TypeError): + save_operator('ping', 'somewhere') + + +class CommutatorTest(unittest.TestCase): + + def setUp(self): + self.fermion_term = FermionOperator('1^ 2^ 3 4', -3.17) + self.fermion_operator = self.fermion_term + hermitian_conjugated( + self.fermion_term) + self.qubit_operator = jordan_wigner(self.fermion_operator) + + def test_commutes_identity(self): + com = commutator(FermionOperator.identity(), + FermionOperator('2^ 3', 2.3)) + self.assertTrue(com.isclose(FermionOperator.zero())) + + def test_commutes_no_intersection(self): + com = commutator(FermionOperator('2^ 3'), FermionOperator('4^ 5^ 3')) + com = normal_ordered(com) + self.assertTrue(com.isclose(FermionOperator.zero())) + + def test_commutes_number_operators(self): + com = commutator(FermionOperator('4^ 3^ 4 3'), FermionOperator('2^ 2')) + com = normal_ordered(com) + self.assertTrue(com.isclose(FermionOperator.zero())) + + def test_commutator_hopping_operators(self): + com = commutator(3 * FermionOperator('1^ 2'), FermionOperator('2^ 3')) + com = normal_ordered(com) + self.assertTrue(com.isclose(FermionOperator('1^ 3', 3))) + + def test_commutator_hopping_with_single_number(self): + com = commutator(FermionOperator('1^ 2', 1j), FermionOperator('1^ 1')) + com = normal_ordered(com) + self.assertTrue(com.isclose(-FermionOperator('1^ 2') * 1j)) + + def test_commutator_hopping_with_double_number_one_intersection(self): + com = commutator(FermionOperator('1^ 3'), FermionOperator('3^ 2^ 3 2')) + com = normal_ordered(com) + self.assertTrue(com.isclose(-FermionOperator('2^ 1^ 3 2'))) + + def test_commutator_hopping_with_double_number_two_intersections(self): + com = commutator(FermionOperator('2^ 3'), FermionOperator('3^ 2^ 3 2')) + com = normal_ordered(com) + self.assertTrue(com.isclose(FermionOperator.zero())) + def test_commutator(self): operator_a = FermionOperator('') self.assertTrue(FermionOperator().isclose( @@ -82,6 +212,6 @@ def test_commutator_operator_b_bad_type_raise_TypeError(self): with self.assertRaises(TypeError): commutator(self.qubit_operator, "hello") - -if __name__ == '__main__': - unittest.main() + def test_commutator_not_same_type(self): + with self.assertRaises(TypeError): + commutator(self.fermion_operator, self.qubit_operator) diff --git a/src/fermilib/utils/_plane_wave_hamiltonian.py b/src/fermilib/utils/_plane_wave_hamiltonian.py index 4da0da3..cd42887 100644 --- a/src/fermilib/utils/_plane_wave_hamiltonian.py +++ b/src/fermilib/utils/_plane_wave_hamiltonian.py @@ -18,16 +18,54 @@ from fermilib.config import * from fermilib.ops import FermionOperator from fermilib.utils._grid import Grid -from fermilib.utils._jellium import (orbital_id, grid_indices, position_vector, - momentum_vector, jellium_model, - jordan_wigner_position_jellium) +from fermilib.utils._jellium import ( + grid_indices, + jellium_model, + jordan_wigner_dual_basis_jellium, + momentum_vector, + orbital_id, + position_vector) from fermilib.utils._molecular_data import periodic_hash_table from projectq.ops import QubitOperator -def dual_basis_u_operator(grid, geometry, spinless): - """Return the external potential operator in plane wave dual basis. +def wigner_seitz_length_scale(wigner_seitz_radius, n_particles, dimension): + """Function to give length_scale associated with Wigner-Seitz radius. + + Args: + wigner_seitz_radius (float): The radius per particle in atomic units. + n_particles (int): The number of particles in the simulation cell. + dimension (int): The dimension of the system. + + Returns: + length_scale (float): The length scale for the simulation. + + Raises: + ValueError: System dimension must be a positive integer. + """ + if not isinstance(dimension, int) or dimension < 1: + raise ValueError('System dimension must be a positive integer.') + + half_dimension = dimension // 2 + if dimension % 2: + volume_per_particle = (2 * numpy.math.factorial(half_dimension) * + (4 * numpy.pi) ** half_dimension / + numpy.math.factorial(dimension) * + wigner_seitz_radius ** dimension) + else: + volume_per_particle = (numpy.pi ** half_dimension / + numpy.math.factorial(half_dimension) * + wigner_seitz_radius ** dimension) + + volume = volume_per_particle * n_particles + length_scale = volume ** (1. / dimension) + + return length_scale + + +def dual_basis_external_potential(grid, geometry, spinless): + """Return the external potential in the dual basis of arXiv:1706.00023. Args: grid (Grid): The discretization to use. @@ -45,7 +83,6 @@ def dual_basis_u_operator(grid, geometry, spinless): spins = [None] else: spins = [0, 1] - for pos_indices in grid.all_points_indices(): coordinate_p = position_vector(pos_indices, grid) for nuclear_term in geometry: @@ -67,11 +104,10 @@ def dual_basis_u_operator(grid, geometry, spinless): operator = FermionOperator(operators, coefficient) else: operator += FermionOperator(operators, coefficient) - return operator -def plane_wave_u_operator(grid, geometry, spinless): +def plane_wave_external_potential(grid, geometry, spinless): """Return the external potential operator in plane wave basis. Args: @@ -122,7 +158,8 @@ def plane_wave_u_operator(grid, geometry, spinless): def plane_wave_hamiltonian(grid, geometry=None, - spinless=False, momentum_space=True): + spinless=False, plane_wave=True, + include_constant=False): """Returns Hamiltonian as FermionOperator class. Args: @@ -131,13 +168,14 @@ def plane_wave_hamiltonian(grid, geometry=None, example is [('H', (0, 0, 0)), ('H', (0, 0, 0.7414))]. Distances in atomic units. Use atomic symbols to specify atoms. spinless (bool): Whether to use the spinless model or not. - momentum_space (bool): Whether to return in plane wave basis (True) + plane_wave (bool): Whether to return in plane wave basis (True) or plane wave dual basis (False). + include_constant (bool): Whether to include the Madelung constant. Returns: FermionOperator: The hamiltonian. """ - jellium_op = jellium_model(grid, spinless, momentum_space) + jellium_op = jellium_model(grid, spinless, plane_wave, include_constant) if geometry is None: return jellium_op @@ -148,10 +186,12 @@ def plane_wave_hamiltonian(grid, geometry=None, if item[0] not in periodic_hash_table: raise ValueError("Invalid nuclear element.") - if momentum_space: - external_potential = plane_wave_u_operator(grid, geometry, spinless) + if plane_wave: + external_potential = plane_wave_external_potential( + grid, geometry, spinless) else: - external_potential = dual_basis_u_operator(grid, geometry, spinless) + external_potential = dual_basis_external_potential( + grid, geometry, spinless) return jellium_op + external_potential @@ -245,7 +285,8 @@ def _fourier_transform_helper(hamiltonian, return hamiltonian_t -def jordan_wigner_dual_basis_hamiltonian(grid, geometry=None, spinless=False): +def jordan_wigner_dual_basis_hamiltonian(grid, geometry=None, spinless=False, + include_constant=False): """Return the dual basis Hamiltonian as QubitOperator. Args: @@ -254,11 +295,13 @@ def jordan_wigner_dual_basis_hamiltonian(grid, geometry=None, spinless=False): example is [('H', (0, 0, 0)), ('H', (0, 0, 0.7414))]. Distances in atomic units. Use atomic symbols to specify atoms. spinless (bool): Whether to use the spinless model or not. + include_constant (bool): Whether to include the Madelung constant. Returns: hamiltonian (QubitOperator) """ - jellium_op = jordan_wigner_position_jellium(grid, spinless) + jellium_op = jordan_wigner_dual_basis_jellium( + grid, spinless, include_constant) if geometry is None: return jellium_op diff --git a/src/fermilib/utils/_plane_wave_hamiltonian_test.py b/src/fermilib/utils/_plane_wave_hamiltonian_test.py index 4c1bc35..343e4f2 100644 --- a/src/fermilib/utils/_plane_wave_hamiltonian_test.py +++ b/src/fermilib/utils/_plane_wave_hamiltonian_test.py @@ -19,19 +19,62 @@ from fermilib.ops import normal_ordered from fermilib.transforms import jordan_wigner -from fermilib.utils import eigenspectrum, Grid +from fermilib.utils import eigenspectrum, Grid, jellium_model from fermilib.utils._plane_wave_hamiltonian import ( - plane_wave_hamiltonian, + dual_basis_external_potential, fourier_transform, inverse_fourier_transform, - plane_wave_u_operator, - dual_basis_u_operator, jordan_wigner_dual_basis_hamiltonian, + plane_wave_external_potential, + plane_wave_hamiltonian, + wigner_seitz_length_scale, ) class PlaneWaveHamiltonianTest(unittest.TestCase): + def test_wigner_seitz_radius_1d(self): + wigner_seitz_radius = 3.17 + n_particles = 20 + one_d_test = wigner_seitz_length_scale( + wigner_seitz_radius, n_particles, 1) + self.assertAlmostEqual( + one_d_test, n_particles * 2. * wigner_seitz_radius) + + def test_wigner_seitz_radius_2d(self): + wigner_seitz_radius = 0.5 + n_particles = 3 + two_d_test = wigner_seitz_length_scale( + wigner_seitz_radius, n_particles, 2) ** 2. + self.assertAlmostEqual( + two_d_test, n_particles * numpy.pi * wigner_seitz_radius ** 2.) + + def test_wigner_seitz_radius_3d(self): + wigner_seitz_radius = 4.6 + n_particles = 37 + three_d_test = wigner_seitz_length_scale( + wigner_seitz_radius, n_particles, 3) ** 3. + self.assertAlmostEqual( + three_d_test, n_particles * (4. * numpy.pi / 3. * + wigner_seitz_radius ** 3.)) + + def test_wigner_seitz_radius_6d(self): + wigner_seitz_radius = 5. + n_particles = 42 + six_d_test = wigner_seitz_length_scale( + wigner_seitz_radius, n_particles, 6) ** 6 + self.assertAlmostEqual( + six_d_test, n_particles * (numpy.pi ** 3 / 6 * + wigner_seitz_radius ** 6)) + + def test_wigner_seitz_radius_bad_dimension_not_integer(self): + with self.assertRaises(ValueError): + wigner_seitz_length_scale(3, 2, dimension=4.2) + + def test_wigner_seitz_radius_bad_dimension_not_positive(self): + with self.assertRaises(ValueError): + wigner_seitz_length_scale(3, 2, dimension=0) + def test_fourier_transform(self): grid = Grid(dimensions=1, scale=1.5, length=3) spinless_set = [True, False] @@ -74,11 +117,13 @@ def test_plane_wave_hamiltonian_integration(self): length_set = [3, 4] spinless_set = [True, False] geometry = [('H', (0,)), ('H', (0.8,))] + length_scale = 1.1 + for l in length_set: for spinless in spinless_set: - grid = Grid(dimensions=1, scale=1.0, length=l) - h_plane_wave = plane_wave_hamiltonian(grid, geometry, spinless, - True) + grid = Grid(dimensions=1, scale=length_scale, length=l) + h_plane_wave = plane_wave_hamiltonian( + grid, geometry, spinless, True, include_constant=True) h_dual_basis = plane_wave_hamiltonian(grid, geometry, spinless, False) jw_h_plane_wave = jordan_wigner(h_plane_wave) @@ -86,24 +131,56 @@ def test_plane_wave_hamiltonian_integration(self): h_plane_wave_spectrum = eigenspectrum(jw_h_plane_wave) h_dual_basis_spectrum = eigenspectrum(jw_h_dual_basis) - diff = numpy.amax(numpy.absolute( - h_plane_wave_spectrum - h_dual_basis_spectrum)) - self.assertAlmostEqual(diff, 0) + max_diff = numpy.amax( + h_plane_wave_spectrum - h_dual_basis_spectrum) + min_diff = numpy.amin( + h_plane_wave_spectrum - h_dual_basis_spectrum) + + self.assertAlmostEqual(max_diff, 2.8372 / length_scale) + self.assertAlmostEqual(min_diff, 2.8372 / length_scale) + + def test_plane_wave_hamiltonian_default_to_jellium_with_no_geometry(self): + grid = Grid(dimensions=1, scale=1.0, length=4) + self.assertTrue(plane_wave_hamiltonian(grid).isclose( + jellium_model(grid))) + + def test_plane_wave_hamiltonian_bad_geometry(self): + grid = Grid(dimensions=1, scale=1.0, length=4) + with self.assertRaises(ValueError): + plane_wave_hamiltonian(grid, geometry=[('H', (0, 0, 0))]) + + def test_plane_wave_hamiltonian_bad_element(self): + grid = Grid(dimensions=3, scale=1.0, length=4) + with self.assertRaises(ValueError): + plane_wave_hamiltonian(grid, geometry=[('Unobtainium', + (0, 0, 0))]) def test_jordan_wigner_dual_basis_hamiltonian(self): grid = Grid(dimensions=2, length=3, scale=1.) spinless = True geometry = [('H', (0, 0)), ('H', (0.5, 0.8))] - fermion_hamiltonian = plane_wave_hamiltonian(grid, geometry, spinless, - False) + fermion_hamiltonian = plane_wave_hamiltonian( + grid, geometry, spinless, False, include_constant=True) qubit_hamiltonian = jordan_wigner(fermion_hamiltonian) - test_hamiltonian = jordan_wigner_dual_basis_hamiltonian(grid, geometry, - spinless) + test_hamiltonian = jordan_wigner_dual_basis_hamiltonian( + grid, geometry, spinless, include_constant=True) self.assertTrue(test_hamiltonian.isclose(qubit_hamiltonian)) - -# Run test. -if __name__ == '__main__': - unittest.main() + def test_jordan_wigner_dual_basis_hamiltonian_default_to_jellium(self): + grid = Grid(dimensions=1, scale=1.0, length=4) + self.assertTrue(jordan_wigner_dual_basis_hamiltonian(grid).isclose( + jordan_wigner(jellium_model(grid, plane_wave=False)))) + + def test_jordan_wigner_dual_basis_hamiltonian_bad_geometry(self): + grid = Grid(dimensions=1, scale=1.0, length=4) + with self.assertRaises(ValueError): + jordan_wigner_dual_basis_hamiltonian( + grid, geometry=[('H', (0, 0, 0))]) + + def test_jordan_wigner_dual_basis_hamiltonian_bad_element(self): + grid = Grid(dimensions=3, scale=1.0, length=4) + with self.assertRaises(ValueError): + jordan_wigner_dual_basis_hamiltonian( + grid, geometry=[('Unobtainium', (0, 0, 0))]) diff --git a/src/fermilib/utils/_sparse_tools.py b/src/fermilib/utils/_sparse_tools.py index 98fdb84..9df2652 100644 --- a/src/fermilib/utils/_sparse_tools.py +++ b/src/fermilib/utils/_sparse_tools.py @@ -14,6 +14,7 @@ from __future__ import absolute_import from functools import reduce +import itertools import numpy import numpy.linalg import scipy @@ -123,7 +124,7 @@ def jordan_wigner_sparse(fermion_operator, n_qubits=None): return sparse_operator -def qubit_operator_sparse(qubit_operator, n_qubits): +def qubit_operator_sparse(qubit_operator, n_qubits=None): """Initialize a SparseOperator from a QubitOperator. Args: @@ -133,9 +134,11 @@ def qubit_operator_sparse(qubit_operator, n_qubits): Returns: The corresponding SparseOperator. """ + from fermilib.utils import count_qubits if n_qubits is None: - from fermilib.utils import count_qubits - n_qubits = count_qubits(fermion_operator) + n_qubits = count_qubits(qubit_operator) + if n_qubits < count_qubits(qubit_operator): + raise ValueError('Invalid number of qubits specified.') # Construct the SparseOperator. n_hilbert = 2 ** n_qubits @@ -198,6 +201,50 @@ def jw_hartree_fock_state(n_electrons, n_orbitals): return psi +def jw_number_indices(n_electrons, n_qubits): + """Return the indices for n_electrons in n_qubits under JW encoding + + Calculates the indices for all possible arrangements of n-electrons + within n-qubit orbitals when a Jordan-Wigner encoding is used. + Useful for restricting generic operators or vectors to a particular + particle number space when desired + + Args: + n_electrons(int): Number of particles to restrict the operator to + n_qubits(int): Number of qubits defining the total state + + Returns: + indices(list): List of indices in a 2^n length array that indicate + the indices of constant particle number within n_qubits + in a Jordan-Wigner encoding. + + """ + occupations = itertools.combinations(range(n_qubits), n_electrons) + indices = [sum([2**n for n in occupation]) + for occupation in occupations] + return indices + + +def jw_number_restrict_operator(operator, n_electrons, n_qubits=None): + """Restrict a Jordan-Wigner encoded operator to a given particle number + + Args: + sparse_operator(ndarray or sparse): Numpy operator acting on + the space of n_qubits. + n_electrons(int): Number of particles to restrict the operator to + n_qubits(int): Number of qubits defining the total state + + Returns: + new_operator(ndarray or sparse): Numpy operator restricted to + acting on states with the same particle number. + """ + if n_qubits is None: + n_qubits = int(numpy.log2(operator.shape[0])) + + select_indices = jw_number_indices(n_electrons, n_qubits) + return operator[numpy.ix_(select_indices, select_indices)] + + def get_density_matrix(states, probabilities): n_qubits = states[0].shape[0] density_matrix = scipy.sparse.csc_matrix( @@ -224,12 +271,12 @@ def get_ground_state(sparse_operator): eigenvalue: The lowest eigenvalue, a float. eigenstate: The lowest eigenstate in scipy.sparse csc format. """ - if is_hermitian(sparse_operator): - values, vectors = scipy.sparse.linalg.eigsh( - sparse_operator, 2, which='SA', maxiter=1e7) - else: - values, vectors = scipy.sparse.linalg.eigs( - sparse_operator, 2, which='SA', maxiter=1e7) + if not is_hermitian(sparse_operator): + raise ValueError('sparse_operator must be Hermitian.') + + values, vectors = scipy.sparse.linalg.eigsh( + sparse_operator, 2, which='SA', maxiter=1e7) + eigenstate = scipy.sparse.csc_matrix(vectors[:, 0]) eigenvalue = values[0] return eigenvalue, eigenstate.getH() @@ -260,7 +307,7 @@ def expectation(sparse_operator, state): A real float giving expectation value. Raises: - SparseOperatorError: Input state has invalid format. + ValueError: Input state has invalid format. """ # Handle density matrix. if state.shape == sparse_operator.shape: @@ -274,7 +321,7 @@ def expectation(sparse_operator, state): else: # Handle exception. - raise SparseOperatorError('Input state has invalid format.') + raise ValueError('Input state has invalid format.') # Return. return expectation @@ -285,11 +332,11 @@ def get_gap(sparse_operator): Returns: A real float giving eigenvalue gap. """ - if is_hermitian(sparse_operator): - values, _ = scipy.sparse.linalg.eigsh( - sparse_operator, 2, which='SA', maxiter=1e7) - else: - values, _ = scipy.sparse.linalg.eigs( - sparse_operator, 2, which='SA', maxiter=1e7) + if not is_hermitian(sparse_operator): + raise ValueError('sparse_operator must be Hermitian.') + + values, _ = scipy.sparse.linalg.eigsh( + sparse_operator, 2, which='SA', maxiter=1e7) + gap = abs(values[1] - values[0]) return gap diff --git a/src/fermilib/utils/_sparse_tools_test.py b/src/fermilib/utils/_sparse_tools_test.py index bd51923..ea0d2ab 100644 --- a/src/fermilib/utils/_sparse_tools_test.py +++ b/src/fermilib/utils/_sparse_tools_test.py @@ -14,11 +14,14 @@ from __future__ import absolute_import import numpy -from scipy.sparse import csc_matrix import unittest -from fermilib.ops import FermionOperator -from fermilib.transforms import jordan_wigner, get_sparse_operator +from scipy.linalg import eigh, norm +from scipy.sparse import csc_matrix + +from fermilib.ops import FermionOperator, number_operator +from fermilib.transforms import get_sparse_operator, jordan_wigner +from fermilib.utils import Grid, jellium_model from fermilib.utils._sparse_tools import * @@ -48,6 +51,104 @@ def test_qubit_jw_fermion_integration(self): self.assertAlmostEqual(0., numpy.amax( numpy.absolute(fermion_spectrum - qubit_spectrum))) + def test_jw_sparse_index(self): + """Test the indexing scheme for selecting specific particle numbers""" + expected = [1, 2] + calculated_indices = jw_number_indices(1, 2) + self.assertEqual(expected, calculated_indices) + + expected = [3] + calculated_indices = jw_number_indices(2, 2) + self.assertEqual(expected, calculated_indices) + + def test_jw_restrict_operator(self): + """Test the scheme for restricting JW encoded operators to number""" + # Make a Hamiltonian that cares mostly about number of electrons + n_qubits = 6 + target_electrons = 3 + penalty_const = 100. + number_sparse = jordan_wigner_sparse(number_operator(n_qubits)) + bias_sparse = jordan_wigner_sparse( + sum([FermionOperator(((i, 1), (i, 0)), 1.0) for i + in range(n_qubits)], FermionOperator())) + hamiltonian_sparse = penalty_const * ( + number_sparse - target_electrons * + scipy.sparse.identity(2**n_qubits)).dot( + number_sparse - target_electrons * + scipy.sparse.identity(2**n_qubits)) + bias_sparse + + restricted_hamiltonian = jw_number_restrict_operator( + hamiltonian_sparse, target_electrons, n_qubits) + true_eigvals, _ = eigh(hamiltonian_sparse.A) + test_eigvals, _ = eigh(restricted_hamiltonian.A) + + self.assertAlmostEqual(norm(true_eigvals[:20] - test_eigvals[:20]), + 0.0) + + def test_jw_restrict_operator_hopping_to_1_particle(self): + hop = FermionOperator('3^ 1') + FermionOperator('1^ 3') + hop_sparse = jordan_wigner_sparse(hop, n_qubits=4) + hop_restrict = jw_number_restrict_operator(hop_sparse, 1, n_qubits=4) + expected = csc_matrix(([1, 1], ([0, 2], [2, 0])), shape=(4, 4)) + + self.assertTrue(numpy.allclose(hop_restrict.A, expected.A)) + + def test_jw_restrict_operator_interaction_to_1_particle(self): + interaction = FermionOperator('3^ 2^ 4 1') + interaction_sparse = jordan_wigner_sparse(interaction, n_qubits=6) + interaction_restrict = jw_number_restrict_operator( + interaction_sparse, 1, n_qubits=6) + expected = csc_matrix(([], ([], [])), shape=(6, 6)) + + self.assertTrue(numpy.allclose(interaction_restrict.A, expected.A)) + + def test_jw_restrict_operator_interaction_to_2_particles(self): + interaction = (FermionOperator('3^ 2^ 4 1') + + FermionOperator('4^ 1^ 3 2')) + interaction_sparse = jordan_wigner_sparse(interaction, n_qubits=6) + interaction_restrict = jw_number_restrict_operator( + interaction_sparse, 2, n_qubits=6) + + dim = 6 * 5 / 2 # shape of new sparse array + # 3^ 2^ 4 1 maps 2**4 + 2 = 18 to 2**3 + 2**2 = 12 and vice versa; + # in the 2-particle subspace (1, 4) and (2, 3) are 7th and 9th. + expected = csc_matrix(([-1, -1], ([7, 9], [9, 7])), shape=(dim, dim)) + + self.assertTrue(numpy.allclose(interaction_restrict.A, expected.A)) + + def test_jw_restrict_operator_hopping_to_1_particle_default_nqubits(self): + interaction = (FermionOperator('3^ 2^ 4 1') + + FermionOperator('4^ 1^ 3 2')) + interaction_sparse = jordan_wigner_sparse(interaction, n_qubits=6) + # n_qubits should default to 6 + interaction_restrict = jw_number_restrict_operator( + interaction_sparse, 2) + + dim = 6 * 5 / 2 # shape of new sparse array + # 3^ 2^ 4 1 maps 2**4 + 2 = 18 to 2**3 + 2**2 = 12 and vice versa; + # in the 2-particle subspace (1, 4) and (2, 3) are 7th and 9th. + expected = csc_matrix(([-1, -1], ([7, 9], [9, 7])), shape=(dim, dim)) + + self.assertTrue(numpy.allclose(interaction_restrict.A, expected.A)) + + def test_jw_restrict_jellium_ground_state_integration(self): + n_qubits = 4 + grid = Grid(dimensions=1, length=n_qubits, scale=1.0) + jellium_hamiltonian = jordan_wigner_sparse( + jellium_model(grid, spinless=False)) + + # 2 * n_qubits because of spin + number_sparse = jordan_wigner_sparse(number_operator(2 * n_qubits)) + + restricted_number = jw_number_restrict_operator(number_sparse, 2) + restricted_jellium_hamiltonian = jw_number_restrict_operator( + jellium_hamiltonian, 2) + + energy, ground_state = get_ground_state(restricted_jellium_hamiltonian) + + number_expectation = expectation(restricted_number, ground_state) + self.assertAlmostEqual(number_expectation, 2) + class JordanWignerSparseTest(unittest.TestCase): @@ -85,6 +186,62 @@ def test_jw_sparse_twobody(self): jordan_wigner_sparse(FermionOperator('2^ 1^ 1 3')).A, expected.A)) + def test_qubit_operator_sparse_n_qubits_too_small(self): + with self.assertRaises(ValueError): + qubit_operator_sparse(QubitOperator('X3'), 1) + + def test_qubit_operator_sparse_n_qubits_not_specified(self): + expected = csc_matrix(([1, 1, 1, 1], ([1, 0, 3, 2], [0, 1, 2, 3])), + shape=(4, 4)) + self.assertTrue(numpy.allclose( + qubit_operator_sparse(QubitOperator('X1')).A, + expected.A)) + -if __name__ == '__main__': - unittest.main() +class GroundStateTest(unittest.TestCase): + def test_get_ground_state_hermitian(self): + ground = get_ground_state(get_sparse_operator( + QubitOperator('Y0 X1') + QubitOperator('Z0 Z1'))) + expected_state = csc_matrix(([1j, 1], ([1, 2], [0, 0])), + shape=(4, 1)).A + expected_state /= numpy.sqrt(2.0) + + self.assertAlmostEqual(ground[0], -2) + self.assertAlmostEqual( + numpy.absolute(expected_state.T.dot(ground[1].A))[0, 0], 1.0) + + def test_get_ground_state_nonhermitian(self): + with self.assertRaises(ValueError): + get_ground_state(get_sparse_operator(1j * QubitOperator('X1'))) + + +class ExpectationTest(unittest.TestCase): + def test_expectation_correct(self): + operator = get_sparse_operator(QubitOperator('X0'), n_qubits=2) + vector = csc_matrix(([1j, 1j], ([1, 3], [0, 0])), shape=(4, 1)) + self.assertAlmostEqual(expectation(operator, vector), 2.0) + + def test_expectation_correct_zero(self): + operator = get_sparse_operator(QubitOperator('X0'), n_qubits=2) + vector = csc_matrix(([1j, -1j, -1j, -1j], + ([0, 1, 2, 3], [0, 0, 0, 0])), shape=(4, 1)) + self.assertAlmostEqual(expectation(operator, vector), 0.0) + + def test_expectation_invalid_state_length(self): + operator = get_sparse_operator(QubitOperator('X0'), n_qubits=2) + vector = csc_matrix(([1j, -1j, -1j], + ([0, 1, 2], [0, 0, 0])), shape=(3, 1)) + with self.assertRaises(ValueError): + expectation(operator, vector) + + +class GetGapTest(unittest.TestCase): + def test_get_gap(self): + operator = QubitOperator('Y0 X1') + QubitOperator('Z0 Z1') + self.assertAlmostEqual(get_gap(get_sparse_operator(operator)), 2.0) + + def test_get_gap_nonhermitian_error(self): + operator = (QubitOperator('X0 Y1', 1 + 1j) + + QubitOperator('Z0 Z1', 1j) + QubitOperator((), 2 + 1j)) + with self.assertRaises(ValueError): + get_gap(get_sparse_operator(operator)) diff --git a/src/fermilib/utils/_trotter_error.py b/src/fermilib/utils/_trotter_error.py index bab6ee3..e70f0c8 100644 --- a/src/fermilib/utils/_trotter_error.py +++ b/src/fermilib/utils/_trotter_error.py @@ -13,7 +13,7 @@ """Module to compute the second order Trotter error.""" from future.utils import iteritems -from math import sqrt +from math import sqrt, ceil from scipy.linalg import expm from fermilib.config import * @@ -143,11 +143,9 @@ def error_bound(terms, tight=False): error = 0.0 if tight: - # return the Frobenius norm of the error operator - # (upper bound on error) - error = sum(abs(coefficient) ** 2 + # return the 1-norm of the error operator (upper bound on error) + error = sum(abs(coefficient) for coefficient in error_operator(terms).terms.values()) - error = sqrt(error) elif not tight: for alpha in range(len(terms)): @@ -166,3 +164,23 @@ def error_bound(terms, tight=False): error += 4.0 * abs(coefficient_a) * error_a ** 2 return error + + +def trotter_steps_required(trotter_error_bound, time, energy_precision): + """Determine the number of Trotter steps for accurate simulation. + + Args: + trotter_error_bound (float): Upper bound on Trotter error in the + state of interest. + time (float): The total simulation time. + energy_precision (float): Acceptable shift in state energy. + + Returns: + The integer minimum number of Trotter steps required for + simulation to the desired precision. + + Notes: + The number of Trotter steps required is an upper bound on the + true requirement, which may be lower. + """ + return int(ceil(time * sqrt(trotter_error_bound / energy_precision))) diff --git a/src/fermilib/utils/_trotter_error_test.py b/src/fermilib/utils/_trotter_error_test.py index df59ced..665fc52 100644 --- a/src/fermilib/utils/_trotter_error_test.py +++ b/src/fermilib/utils/_trotter_error_test.py @@ -125,15 +125,13 @@ def test_error_operator_all_diagonal(self): class ErrorBoundTest(unittest.TestCase): def test_error_bound_xyz_tight(self): terms = [QubitOperator('X1'), QubitOperator('Y1'), QubitOperator('Z1')] - expected = sqrt(7. / 12) # norm of [[-2/3, 1/3+i/6], [1/3-i/6, 2/3]] - self.assertTrue(numpy.isclose( - error_bound(terms, tight=True), expected)) + expected = sqrt(7. / 12) # 2-norm of [[-2/3, 1/3+i/6], [1/3-i/6, 2/3]] + self.assertLess(expected, error_bound(terms, tight=True)) def test_error_bound_xyz_loose(self): terms = [QubitOperator('X1'), QubitOperator('Y1'), QubitOperator('Z1')] self.assertTrue(numpy.isclose( - error_bound(terms, tight=False), - 4. * (2**2 + 1**2))) + error_bound(terms, tight=False), 4. * (2 ** 2 + 1 ** 2))) def test_error_operator_xyz(self): terms = [QubitOperator('X1'), QubitOperator('Y1'), QubitOperator('Z1')] @@ -146,6 +144,20 @@ def test_error_operator_xyz(self): self.assertTrue(numpy.allclose(matrix, expected), ("Got " + str(matrix))) + def test_error_bound_qubit_tight_less_than_loose_integration(self): + terms = [QubitOperator('X1'), QubitOperator('Y1'), QubitOperator('Z1')] + self.assertLess(error_bound(terms, tight=True), + error_bound(terms, tight=False)) + + +class TrotterStepsRequiredTest(unittest.TestCase): + def test_trotter_steps_required(self): + self.assertEqual(trotter_steps_required( + trotter_error_bound=0.3, time=2.5, energy_precision=0.04), 7) + + def test_trotter_steps_required_negative_time(self): + self.assertEqual(trotter_steps_required( + trotter_error_bound=0.1, time=3.3, energy_precision=0.11), 4) -if __name__ == '__main__': - unittest.main() + def test_return_type(self): + self.assertIsInstance(trotter_steps_required(0.1, 0.1, 0.1), int) diff --git a/src/fermilib/utils/_unitary_cc.py b/src/fermilib/utils/_unitary_cc.py index 980770b..9fcb206 100644 --- a/src/fermilib/utils/_unitary_cc.py +++ b/src/fermilib/utils/_unitary_cc.py @@ -44,12 +44,12 @@ def uccsd_operator(single_amplitudes, double_amplitudes, anti_hermitian=True): 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.) + 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.) + 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 @@ -90,10 +90,10 @@ def convert_amplitude_format(single_amplitudes, double_amplitudes): Args: single_amplitudes(ndarray): [NxN] array storing single excitation - amplitudes corresponding to t[i,j] * (a_i^\dagger a_j + H.C.) + 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.) + 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 diff --git a/src/fermilib/utils/_unitary_cc_test.py b/src/fermilib/utils/_unitary_cc_test.py index 6ab3408..4a32348 100644 --- a/src/fermilib/utils/_unitary_cc_test.py +++ b/src/fermilib/utils/_unitary_cc_test.py @@ -214,7 +214,3 @@ def test_sparse_uccsd_operator_list_inputs(self): (-0.23423) * FermionOperator("1^ 4 6^ 13") + 0.23423 * FermionOperator("13^ 6 4^ 1")) self.assertTrue(test_generator.isclose(generator)) - -# Run test. -if __name__ == '__main__': - unittest.main()