├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── tests.yml ├── .gitignore ├── .pylintrc ├── CITATION.bib ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── docs ├── 1-tutorials │ ├── tutorial_1_H2_molecule_statevector_simulator.ipynb │ ├── tutorial_2_H2O_molecule_statevector_simulator.ipynb │ ├── tutorial_3_H2_arbitrary_hamiltonian.ipynb │ ├── tutorial_4_H2_qasm_simulator.ipynb │ └── tutorial_5_H2_equilibrium_distance.ipynb ├── 2-reference_guide │ └── reference_guide.md ├── 3-explanatory_material │ ├── explanatory_material.md │ └── figs │ │ ├── Fig_5_c.png │ │ └── forging_info_graphic.png └── images │ └── ef_image.png ├── entanglement_forging ├── __init__.py ├── core │ ├── __init__.py │ ├── cholesky_hamiltonian.py │ ├── classical_energies.py │ ├── entanglement_forged_config.py │ ├── forged_operator.py │ ├── orbitals_to_reduce.py │ └── wrappers │ │ ├── __init__.py │ │ ├── entanglement_forged_driver.py │ │ ├── entanglement_forged_ground_state_eigensolver.py │ │ ├── entanglement_forged_vqe.py │ │ └── entanglement_forged_vqe_result.py └── utils │ ├── __init__.py │ ├── bootstrap_result.py │ ├── combined_result.py │ ├── copysample_circuits.py │ ├── forging_subroutines.py │ ├── generic_execution_subroutines.py │ ├── legacy │ ├── __init__.py │ ├── base_operator.py │ ├── common.py │ ├── op_converter.py │ ├── pauli_graph.py │ ├── tpb_grouped_weighted_pauli_operator.py │ └── weighted_pauli_operator.py │ ├── log.py │ ├── meas_mit_filters_faster.py │ ├── meas_mit_fitters_faster.py │ ├── prepare_bitstring.py │ └── pseudorichardson.py ├── pyproject.toml ├── setup.cfg ├── tests ├── __init__.py ├── unit_tests │ ├── __init__.py │ └── test_orbitals_to_reduce.py └── wrappers │ ├── __init__.py │ ├── test_data │ ├── CH3_one_body.npy │ ├── CH3_two_body.npy │ ├── CN_one_body.npy │ ├── CN_two_body.npy │ ├── O2_one_body.npy │ ├── O2_two_body.npy │ ├── OH_one_body.npy │ ├── OH_two_body.npy │ ├── TS_one_body.npy │ ├── TS_two_body.npy │ └── two_body_alpha_alpha.npy │ └── test_entanglement_forged_ground_state_eigensolver.py └── tox.ini /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: "Create a report to help us improve \U0001F914." 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Give some steps that show the bug. A minimal working example of code with output is best. If you are copying in code, please remember to enclose it in triple backticks, ``` [multiline code goes here] ```, so that it displays correctly. 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Additional context** 20 | Add any other context about the problem here. 21 | 22 | **Any suggestions?** 23 | Not required, but if you have suggestions for how a contributor should fix this, or any problems we should be aware of, let us know. 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: "Suggest an idea for this project \U0001F4A1!" 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: [main] 7 | schedule: 8 | - cron: '0 1 * * *' 9 | 10 | jobs: 11 | test_linux: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | include: 16 | - python-version: 3.7 17 | TOXENV: "py37" 18 | - python-version: 3.8 19 | TOXENV: "py38" 20 | - python-version: 3.9 21 | TOXENV: "py39" 22 | 23 | steps: 24 | - uses: actions/checkout@v2 25 | with: 26 | fetch-depth: 0 27 | - name: Set up Python ${{ matrix.python-version }} 28 | uses: actions/setup-python@v2 29 | with: 30 | python-version: ${{ matrix.python-version }} 31 | - name: Install Dependencies 32 | run: pip install --upgrade pip tox --user 33 | - name: Test with tox 34 | env: 35 | TOXENV: ${{ matrix.TOXENV }} 36 | run: python -m tox 37 | 38 | test_macOS: 39 | runs-on: macOS-latest 40 | strategy: 41 | matrix: 42 | include: 43 | - python-version: 3.7 44 | TOXENV: "py37" 45 | - python-version: 3.8 46 | TOXENV: "py38" 47 | - python-version: 3.9 48 | TOXENV: "py39" 49 | 50 | steps: 51 | - uses: actions/checkout@v2 52 | with: 53 | fetch-depth: 0 54 | - name: Set up Python ${{ matrix.python-version }} 55 | uses: actions/setup-python@v2 56 | with: 57 | python-version: ${{ matrix.python-version }} 58 | - name: Install Dependencies 59 | run: pip install --upgrade pip tox --user 60 | - name: Test with tox 61 | env: 62 | TOXENV: ${{ matrix.TOXENV }} 63 | run: python -m tox 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | pip-wheel-metadata/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 97 | __pypackages__/ 98 | 99 | # Celery stuff 100 | celerybeat-schedule 101 | celerybeat.pid 102 | 103 | # SageMath parsed files 104 | *.sage.py 105 | 106 | # Environments 107 | .env 108 | .venv 109 | env/ 110 | venv/ 111 | ENV/ 112 | env.bak/ 113 | venv.bak/ 114 | 115 | # Spyder project settings 116 | .spyderproject 117 | .spyproject 118 | 119 | # Rope project settings 120 | .ropeproject 121 | 122 | # mkdocs documentation 123 | /site 124 | 125 | # mypy 126 | .mypy_cache/ 127 | .dmypy.json 128 | dmypy.json 129 | 130 | # Pyre type checker 131 | .pyre/ 132 | -------------------------------------------------------------------------------- /CITATION.bib: -------------------------------------------------------------------------------- 1 | @misc{entanglement-forging, 2 | author = {Luciano Bello and Agata M. Bra\'{n}czyk and Sergey Bravyi and Andrew Eddins and Julien Gacon and Tanvi P. Gujarati and Ikko Hamamura and Takashi Imamichi and Caleb Johnson and Ieva Liepuoniute and Mario Motta and Max Rossmannek and Travis L. Scholten and Iskandar Sitdikov and Stefan Woerner}, 3 | title = {Entanglement forging module}, 4 | howpublished = {\url{https://github.com/qiskit-community/prototype-entanglement-forging}}, 5 | year = {2021} 6 | } 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | The Qiskit Community is dedicated to our values of treating every individual 6 | with respect and dignity. In the interest of fostering an open and welcoming 7 | environment, all participants, including attendees, speakers, sponsors, 8 | volunteers, online contributors, and IBM employees are expected to show 9 | courtesy for each other and our community by creating a harassment-free 10 | experience for everyone, regardless of age, personal appearance, disability, 11 | ethnicity, gender identity and expression, body size, level of experience, 12 | nationality, race, religion, caste, or sexual identity and orientation. 13 | Expected behavior applies to both online and offline engagement within the 14 | Qiskit Community. 15 | 16 | ## Scope 17 | 18 | The purpose of this Code of Conduct is to define and enforce the values and 19 | conduct of contributors and participants in the Qiskit open source community. 20 | The Code of Conduct applies both within project spaces and in public spaces 21 | when an individual is engaging with the Qiskit open source community. Examples 22 | include attending a Qiskit event, contributing to online projects, commentary 23 | on Slack, or representing a project or community, including using an official 24 | project e-mail address, posting via an official social media account, or 25 | acting as an appointed representative at an online or offline event. 26 | Representation of a project may be further defined and clarified by project 27 | maintainers. 28 | 29 | ## Our Standards 30 | 31 | Examples of behavior that contributes to creating a positive environment 32 | include: 33 | 34 | - Using welcoming and inclusive language 35 | - Being respectful of differing viewpoints, experiences, and cultures 36 | - Gracefully accepting constructive criticism 37 | - Focusing on what is best for the community 38 | - Showing empathy towards other community members 39 | - Being mindful of your surroundings and your fellow participants and listening 40 | to others 41 | - Valuing the contributions of all participants 42 | - Engaging in collaboration before conflict 43 | - Pointing out unintentionally racist, sexist, casteist, or biased comments and 44 | jokes made by community members when they happen 45 | 46 | Examples of unacceptable behavior by participants, even when presented as 47 | "ironic" or "joking," include: 48 | 49 | - The use of sexualized language or imagery and unwelcome physical contact, 50 | sexual attention, or advances 51 | - Trolling, insulting/derogatory comments, and personal or political attacks 52 | - Public or private harassment, including offensive or degrading language 53 | - Publishing others' private information, such as a physical or electronic 54 | address, without explicit permission. This includes any sort of "outing" of 55 | any aspect of someone's identity without their consent. 56 | - "Doxxing," Publishing screenshots or quotes, especially from identity slack 57 | channels, private chat, or public events, without all quoted users' explicit 58 | consent. 59 | - Other conduct which could reasonably be considered inappropriate in a 60 | professional setting 61 | 62 | ## Responsibilities & Enforcement 63 | 64 | The entire Qiskit community is responsible for upholding the terms of the Code 65 | of Conduct in Qiskit Community events and spaces and reporting violations if 66 | they see them. The internal Qiskit team at IBM is ultimately responsible for 67 | clarifying the standards of acceptable behavior and enforcement, and is expected 68 | to take appropriate and fair corrective action in response to any instances of 69 | unacceptable behavior. 70 | 71 | If a participant or contributor engages in negative or harmful behavior, IBM 72 | will take any action they deem appropriate, including but not limited to 73 | issuing warnings, expulsion from an event with no refund, deleting comments, 74 | permanent banning from future events or online community, or calling local law 75 | enforcement. IBM has the right and responsibility to remove, edit, or reject 76 | comments, commits, code, wiki edits, issues, and other contributions that are 77 | not aligned to this Code of Conduct, or to temporarily or permanently ban any 78 | contributor or participant for other behaviors that they deem inappropriate, 79 | threatening, offensive, or harmful. 80 | 81 | If you see a Code of Conduct violation: 82 | 83 | 1. If you feel comfortable, let the person know that what they did is not 84 | appropriate and ask them to stop and/or edit or delete their message(s) or 85 | comment(s). 86 | 2. If the person does not immediately stop the behavior or correct the issue, 87 | or if you're uncomfortable speaking up, flag a moderator and, if appropriate, 88 | fill out the anonymous 89 | [Code of Conduct violation form](https://airtable.com/shrl5mEF4Eun1aIDm). 90 | 3. The Qiskit Community will open an investigation upon receiving your form 91 | entry. When reporting, please include any relevant details, links, 92 | screenshots, context, or other information that may be used to better 93 | understand and resolve the situation. 94 | 4. If the code of conduct violation occurs at an event and requires immediate 95 | response or contains a concern about an individual attending an upcoming 96 | event, contact the event's on-call Code of Conduct point of contact listed 97 | in the event specific code of conduct document. If you don't feel comfortable 98 | speaking to the point of contact in person, fill out a Code of Conduct 99 | violation form entry and include the details of the event so that the Code of 100 | Conduct enforcement board can contact the event's on-call Code of Conduct 101 | point of contact. 102 | 5. If an IBM employee witnesses a Code of Conduct violation at any time, such as 103 | at events, in a Slack channel, or open source forums, it is their 104 | responsibility to file a Code of Conduct violation report. 105 | 106 | This Code of Conduct does not supersede existing IBM corporate policies, such as 107 | the IBM Business Conduct Guidelines and IBM Business Partner Code of Conduct. 108 | IBM employees must follow IBM's Business Conduct Guidelines. IBM's business 109 | partners must follow the IBM Business Partner Code of Conduct. IBM employees 110 | concerned with a fellow IBMer's behavior should follow IBM's own internal HR 111 | reporting protocols, which include engaging the offending IBMer's manager and 112 | involving IBM Concerns and Appeals. IBM employees concerned with an IBM 113 | business partner's behavior should notify tellibm@us.ibm.com. 114 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | __We appreciate all kinds of help, so thank you!__ 3 | 4 | 5 | ## Code contribution guide 6 | This guide is for those who want to extend the module or documentation. If you just want to use the package, read [this other guide](./docs/2-reference_guide/reference_guide.md) instead. 7 | 8 | Code in this repository should conform to PEP8 standards. Style/lint checks are run to validate this. Line length must be limited to no more than 100 characters. 9 | 10 | ### Initial set-up and installing dependencies 11 | In order to contribute, you will need to [install the module from source](./docs/2-reference_guide/reference_guide.md#installation-from-source). If you do not have write permissions to the original Entanglement Forging repo, you will need to fork it to your personal account first, and submit all Pull Requests (PR) from there. Even if you have write permissions, forking the repo should always work, so this is the recommended approach. 12 | 13 | ### Running tests 14 | If you haven't already, install the repo in editable mode and with developer dependencies: 15 | ``` 16 | pip install -e .[dev] 17 | ``` 18 | 19 | To run tests: 20 | ``` 21 | tox -e{env} 22 | ``` 23 | where you replace `{env}` with `py37`, `py38` or `py39` depending on which version of python you have (to check python version, type `python --version` in the terminal). 24 | 25 | To run linting tests (checks formatting/syntax): 26 | ``` 27 | tox -elint 28 | ``` 29 | 30 | To run notebook tests (for more info, see [here](https://github.com/ReviewNB/treon)): 31 | ``` 32 | treon docs/ 33 | ``` 34 | Note: notebook tests check for execution and time-out errors, not correctness. 35 | 36 | ### Making a pull request 37 | 38 | #### Step-by-step 39 | 1. To make a contribution, first set up a remote branch (here called `my-contribution`) either in your fork (i.e. `origin`) or the original repo (i.e. `upstream`). In the absence of a fork, the (only) remote will simply be referred to all the time by the name `origin` (i.e. replace `upstream` in all commands): 40 | ``` 41 | git checkout main 42 | git pull origin 43 | git checkout -b my-contribution 44 | ``` 45 | ... make your contribution now (edit some code, add some files) ... 46 | ``` 47 | git add . 48 | git commit -m 'initial working version of my contribution' 49 | git push -u origin my-contribution 50 | ``` 51 | 2. Before making a Pull Request always get the latest changes from `main` (`upstream` if there is a fork, `origin` otherwise): 52 | ``` 53 | git checkout main 54 | git pull upstream 55 | git checkout my-contribution 56 | git merge main 57 | ``` 58 | ... fix any merge conflicts here ... 59 | ``` 60 | git add . 61 | git commit -m 'merged updates from main' 62 | git push 63 | ``` 64 | 3. Go back to the appropriate Entanglement Forging repo on GitHub (i.e. fork or original), switch to your contribution branch (same name: `my-contribution`), and click _"Pull Request"_. Write a clear explanation of the feature. 65 | 4. Under _Reviewer_, select Aggie Branczyk __and__ Caleb Johnson. 66 | 5. Click _"Create Pull Request"_. 67 | 6. Your Pull Request will be reviewed and, if everything is ok, it will be merged. 68 | 69 | #### Pull request checklist 70 | When submitting a pull request and you feel it is ready for review, please ensure that: 71 | 1. The code follows the _code style_ of this project and 72 | 2. successfully passes the _unit tests_. 73 | 74 | Entanglement Forging uses [Pylint](https://www.pylint.org) and [PEP8](https://www.python.org/dev/peps/pep-0008) style guidelines. For this you can run: 75 | ``` 76 | tox -elint 77 | ``` 78 | 79 | 80 | ## Other ways of contributing 81 | Other than submitting new source code, users can contribute in the following meaningful ways: 82 | - __Reporting Bugs and Requesting Features__: Users are encouraged to use Github Issues for reporting issues are requesting features. 83 | - __Ask/Answer Questions and Discuss Entanglement Forging__: Users are encouraged to use Github Discussions for engaging with researchers, developers, and other users regarding this project. 84 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2021 IBM and its contributors 2 | 3 | Apache License 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following 183 | boilerplate notice, with the fields enclosed by brackets "[]" 184 | replaced with your own identifying information. (Don't include 185 | the brackets!) The text should be enclosed in the appropriate 186 | comment syntax for the file format. We also recommend that a 187 | file or class name and description of purpose be included on the 188 | same "printed page" as the copyright notice for easier 189 | identification within third-party archives. 190 | 191 | Copyright 2017 IBM and its contributors. 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. 204 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | :triangular_flag_on_post: **This repository has been archived. A more updated version of entanglement forging is available with [circuit-knitting-toolbox v0.5](https://pypi.org/project/circuit-knitting-toolbox/0.5.0/) and prior.** 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | ![Platform](https://img.shields.io/badge/Platform-Linux%20%7C%20macOS-informational) 10 | [![Python](https://img.shields.io/badge/Python-3.7%20%7C%203.8%20%7C%203.9-informational)](https://www.python.org/) 11 | [![Qiskit](https://img.shields.io/badge/Qiskit-%E2%89%A5%200.34.1-6133BD)](https://github.com/Qiskit/qiskit) 12 | [![Qiskit Nature](https://img.shields.io/badge/Qiskit%20Nature-%E2%89%A5%200.3.0-6133BD)](https://github.com/Qiskit/qiskit-nature) 13 |
14 | [![License](https://img.shields.io/github/license/qiskit-community/prototype-entanglement-forging?label=License)](LICENSE.txt) 15 | [![Tests](https://github.com/qiskit-community/prototype-entanglement-forging/actions/workflows/tests.yml/badge.svg)](https://github.com/qiskit-community/prototype-entanglement-forging/actions/workflows/tests.yml) 16 | 17 |
18 | 19 |

20 | 21 | Logo 22 | 23 |

Entanglement Forging

24 | 25 |

26 | 27 | Launch Demo 28 | 29 | 30 | Watch Video 31 | 32 |

33 |

34 | 35 | 36 | 37 | 38 | ### Table of Contents 39 | * [Tutorials](docs/1-tutorials/) 40 | * [Reference Guide](docs/2-reference_guide/reference_guide.md) 41 | * [Explanatory Material](docs/3-explanatory_material/explanatory_material.md) 42 | * [About This Project](#about-this-project) 43 | * [How to Give Feedback](#how-to-give-feedback) 44 | * [Contribution Guidelines](#contribution-guidelines) 45 | * [Acknowledgements](#acknowledgements) 46 | * [About Prototypes](#about-prototypes) 47 | * [References](#references) 48 | * [License](#license) 49 | 50 | 51 | ---------------------------------------------------------------------------------------------------- 52 | 53 | 54 | ### About This Project 55 | This module allows a user to simulate chemical and physical systems using a Variational Quantum Eigensolver (VQE) enhanced by Entanglement Forging [[1]](#references). Entanglement Forging doubles the size of the system that can be *exactly* simulated on a fixed set of quantum bits. 56 | 57 | Before using the module for new work, users should read through the [reference guide](./docs/2-reference_guide/reference_guide.md) and the [explanatory material](docs/3-explanatory_material/explanatory_material.md), specifically the [current limitations](docs/3-explanatory_material/explanatory_material.md#%EF%B8%8F-current-limitations) of the module. 58 | 59 | 60 | ---------------------------------------------------------------------------------------------------- 61 | 62 | 63 | ### How to Give Feedback 64 | We encourage your feedback! You can share your thoughts with us by: 65 | - [Opening an issue](https://github.com/qiskit-community/prototype-entanglement-forging/issues) in the repository 66 | - [Starting a conversation on GitHub Discussions](https://github.com/qiskit-community/prototype-entanglement-forging/discussions) 67 | - Filling out our [survey](https://airtable.com/shrFxJXYzjxf5tFvx) 68 | 69 | 70 | ---------------------------------------------------------------------------------------------------- 71 | 72 | 73 | ### Contribution Guidelines 74 | For information on how to contribute to this project, please take a look at [CONTRIBUTING.MD](CONTRIBUTING.md). 75 | 76 | 77 | ---------------------------------------------------------------------------------------------------- 78 | 79 | 80 | ### Acknowledgements 81 | This module is based on the theory and experiment described in [[1]](#references). 82 | 83 | The initial code on which this module is based was written by Andrew Eddins, Mario Motta, Tanvi Gujarati, and Charles Hadfield. The module was developed by Aggie Branczyk, Iskandar Sitdikov, and Luciano Bello, with help from Caleb Johnson, Mario Motta, Andrew Eddins, Tanvi Gujarati, Stefan Wörner, Max Rossmannek, Ikko Hamamura, and Takashi Imamichi. The documentation was written by Aggie Branczyk, with help from Ieva Liepuoniute, Mario Motta and Travis Scholten. 84 | 85 | We also thank Lev Bishop, Sarah Sheldon, and John Lapeyre for useful discussions. 86 | 87 | 88 | ---------------------------------------------------------------------------------------------------- 89 | 90 | 91 | ### About Prototypes 92 | 93 | Prototypes is a collaboration between developers and researchers that will give users access to prototypes from cutting-edge research in areas like quantum simulation and machine learning. These software packages are built on top of, and may eventually be integrated into the Qiskit SDK. They are a contribution as part of the Qiskit community. 94 | 95 | Check out our [blog post](https://medium.com/qiskit/try-out-the-latest-advances-in-quantum-computing-with-ibm-quantum-prototypes-11f51124cb61) for more information! 96 | 97 | 98 | ---------------------------------------------------------------------------------------------------- 99 | 100 | 101 | ### References 102 | [1] Andrew Eddins, Mario Motta, Tanvi P. Gujarati, Sergey Bravyi, Antonio Mezzacapo, Charles Hadfield, Sarah Sheldon, *Doubling the size of quantum simulators by entanglement forging*, https://journals.aps.org/prxquantum/abstract/10.1103/PRXQuantum.3.010309 103 | 104 | 105 | ---------------------------------------------------------------------------------------------------- 106 | 107 | 108 | ### License 109 | [Apache License 2.0](LICENSE.txt) 110 | -------------------------------------------------------------------------------- /docs/2-reference_guide/reference_guide.md: -------------------------------------------------------------------------------- 1 | # Reference guide for the entanglement forging module 2 | 3 | ## Table of Contents 4 | 1. [Installation instructions](#installation-instructions) 5 | - [Basic installation](#basic-installation) 6 | - [Installation from source](#installation-from-source) 7 | 2. [Using the module](#using-the-module) 8 | - [Specifying the problem](#specifying-the-problem) 9 | - [Specifying the bitstrings](#specifying-the-bitstrings) 10 | - [Freezing orbitals](#freezing-orbitals) 11 | - [Specifying the Ansatz](#specifying-the-ansatz) 12 | - [Options (`EntanglementForgedConfig`)](#options-entanglementforgedconfig) 13 | - [Specifying the converter](#specifying-the-converter) 14 | - [The solver](#the-solver) 15 | - [Running the algorithm](#running-the-algorithm) 16 | - [Viewing the results](#viewing-the-results) 17 | - [Verbose](#verbose) 18 | 3. [Troubleshooting](#troubleshooting) 19 | - [Getting poor results on the hardware](#getting-poor-results-on-the-hardware) 20 | - [For IBM Power users](#for-ibm-power-users) 21 | 22 | This guide is for those who just want to use the package. If you want to extend the module or documentation, read [this other guide](/CONTRIBUTING.md) instead. Installation instructions are only located here to avoid repetition. 23 | 24 | 25 | ## Installation instructions 26 | :exclamation: _This prototype depends on the PySCF package, which does not support Windows; therefore, Windows users will not be able to install and use this software. Advanced Windows users may wish to attempt to install PySCF using Ubuntu via the Windows Subsystem for Linux. We are exploring the possibility of providing Docker support for this prototype so it can be used within Docker Desktop, including on Windows._ 27 | 28 | Ensure your local environment is compatible with the entanglement-forging package: 29 | - Ensure you are on a supported operating system (macOS or Linux) 30 | - Ensure you are running a supported version of Python (py37,py38,py39) 31 | - (Optional) It can be useful to create a new environment (here called `my_forging_env`) and install Python 3.9 (recommended). There are several alternatives for this, using `conda` within the terminal: 32 | ``` 33 | conda create -n my_forging_env python=3.9 34 | conda activate my_forging_env 35 | ``` 36 | 37 | ### Basic installation 38 | 1. From the terminal, use pip to install the entanglement-forging package: 39 | ``` 40 | pip install entanglement-forging 41 | ``` 42 | 2. Users may now run the entanglement forging demo notebooks on their local machine or use the entanglement-forging package in their own software. 43 | 44 | ### Installation from source 45 | 0. Make sure you have [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) and [pip](https://pip.pypa.io/en/stable/installation/) (and optionally [Miniconda](https://docs.conda.io/en/latest/miniconda.html)) installed. 46 | 1. From the terminal, clone repository: 47 | ``` 48 | git clone https://github.com/qiskit-community/prototype-entanglement-forging.git 49 | ``` 50 | Alternatively, instead of cloning the original repository, you may choose to clone your personal [fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo). You can do so by using the appropriate URL and adding the original repo to the list of remotes (here under the name `upstream`). This will be requiered for contribution unless you are granted write permissions for the original repository. 51 | ``` 52 | git clone 53 | git remote add upstream https://github.com/qiskit-community/prototype-entanglement-forging.git 54 | ``` 55 | 2. Change directory to the freshly cloned forging module: 56 | ``` 57 | cd prototype-entanglement-forging 58 | ``` 59 | 3. Install the dependencies needed: 60 | ``` 61 | pip install . 62 | ``` 63 | 4. (Optional) Install the repo in editable mode and with developer dependencies for contributing: 64 | ``` 65 | pip install -e .[dev] 66 | ``` 67 | 68 | 69 | ## Using the module 70 | 71 | ### Specifying the problem 72 | The module supports two options to specify the problem. 73 | 74 | _Option 1_: with the `ElectronicStructureProblem` object from Qiskit. 75 | ```python 76 | problem = ElectronicStructureProblem( 77 | PySCFDriver(molecule=Molecule(geometry=[('H', [0., 0., 0.]), 78 | ('H', [0., 0., 0.735])], 79 | charge=0, multiplicity=1), 80 | basis='sto3g') 81 | ) 82 | ``` 83 | 84 | _Option 2_: specifying the properties of the system directly to the `EntanglementForgedDriver` object. 85 | ```python 86 | # Coefficients that define the one-body terms of the Hamiltonian 87 | hcore = np.array([ 88 | [-1.12421758, -0.9652574], 89 | [-0.9652574,- 1.12421758] 90 | ]) 91 | 92 | # Coefficients that define the two-body terms of the Hamiltonian 93 | mo_coeff = np.array([ 94 | [0.54830202, 1.21832731], 95 | [0.54830202, -1.21832731] 96 | ]) 97 | 98 | # Coefficients for the molecular orbitals 99 | eri = np.array([ 100 | [[[0.77460594, 0.44744572], [0.44744572, 0.57187698]], 101 | [[0.44744572, 0.3009177], [0.3009177, 0.44744572]]], 102 | [[[0.44744572, 0.3009177], [0.3009177, 0.44744572]], 103 | [[0.57187698, 0.44744572], [0.44744572, 0.77460594]]] 104 | ]) 105 | 106 | driver = EntanglementForgedDriver(hcore=hcore, 107 | mo_coeff=mo_coeff, 108 | eri=eri, 109 | num_alpha=1, 110 | num_beta=1, 111 | nuclear_repulsion_energy=0.7199689944489797) 112 | 113 | problem = ElectronicStructureProblem(driver) 114 | ``` 115 | 116 | The second option is useful when: 117 | 1. you don't want to study a molecule (situations where there is no driver, so you want to feed the Hamiltonian by by-passing pyscf driver) 118 | 2. you want to manipulate the electronic structure of the system in a way that is not supported by the driver (molecular, but not in standard tool kit) 119 | 120 | ### Specifying the bitstrings 121 | Bitstrings are specified as a list of lists. 122 | 123 | _Example 1_: Two qubits with two bitstrings. 124 | ```python 125 | bitstrings = [[1,0],[0,1]] 126 | ``` 127 | _Example 2_: Seven qubits with three bitstrings. 128 | ```python 129 | bitstrings = bitstrings = [[1,1,1,1,1,0,0],[1,0,1,1,1,0,1],[1,0,1,1,1,1,0]] 130 | ``` 131 | 132 | For information on picking bitstrings, refer to [this section](/docs/3-explanatory_material/explanatory_material.md#picking-the-bitstrings) of the Explanatory Material. 133 | 134 | For current limitations on specifying bitstrings, refer to [this section](/docs/3-explanatory_material/explanatory_material.md#ansatz--bitstrings) of the Explanatory Material. 135 | 136 | ### Freezing orbitals 137 | Orbitals can be frozen by specifying them as a list, then using `reduce_bitstrings`. 138 | 139 | _Example_: freezing the zeroth orbital (ground state) and the third orbital. 140 | ```python 141 | orbitals_to_reduce = [0,3] 142 | bitstrings = [[1,1,1,1,1,0,0],[1,0,1,1,1,0,1],[1,0,1,1,1,1,0]] 143 | reduced_bitstrings = reduce_bitstrings(bitstrings, orbitals_to_reduce) 144 | ``` 145 | _Output_: 146 | ```python 147 | >>> reduced_bitstrings 148 | [1,1,1,0,0],[0,1,1,0,1],[0,1,1,1,0]] 149 | ``` 150 | 151 | For discussion of scaling and orbital freezing, refer to the [this section](/docs/3-explanatory_material/explanatory_material.md#freezing-orbitals) of the Explanatory Material. 152 | 153 | ### Specifying the Ansatz 154 | The module supports two options to specify the ansatz. 155 | 156 | _Option 1_: Using a parameterized circuit. 157 | ```python 158 | from qiskit.circuit.library import TwoLocal 159 | 160 | ansatz = TwoLocal(2, [], 'cry', [[0,1],[1,0]], reps=1) 161 | ``` 162 | 163 | _Option 2_: Using parametrized gates. 164 | ```python 165 | from qiskit.circuit import Parameter, QuantumCircuit 166 | 167 | theta_0, theta_1 = Parameter('θ0'), Parameter('θ1') 168 | 169 | ansatz = QuantumCircuit(2) 170 | ansatz.cry(theta_0, 0, 1) 171 | ansatz.cry(theta_1, 1, 0) 172 | ``` 173 | 174 | ### Options (`EntanglementForgedConfig`) 175 | `EntanglementForgedConfig` contains all of the options for running the algorithm. It can be defined as follows: 176 | ``` 177 | config = EntanglementForgedConfig(backend=backend, maxiter = 200, initial_params=[0,0.5*np.pi]) 178 | ``` 179 | 180 | The options are: 181 | - `backend`: Instance of selected backend. 182 | - `qubit_layout`: Initial position of virtual qubits on physical qubits. If this layout makes the circuit compatible with the coupling_map constraints, it will be used. 183 | - `initial_params` (NoneType or list of int): A list specifying the initial optimization parameters. 184 | - `maxiter` (int): Maximum number of optimizer iterations to perform. 185 | - `optimizer_name` (str): e.g. 'SPSA', 'ADAM', 'NELDER_MEAD', 'COBYLA', 'L_BFGS_B', 'SLSQP' ... 186 | - `optimizer_tol` (float): Optimizer tolerance, e.g. 1e-6. 187 | - `skip_any_optimizer_cal` (bool): Setting passed to any optimizer with a 'skip_calibration' argument. 188 | - `spsa_last_average` (int): Number of times to average over final SPSA evaluations to determine optimal parameters (only used for SPSA). 189 | - `initial_spsa_iteration_idx` (int): Iteration index to resume interrupted VQE run (only used for SPSA). 190 | - `spsa_c0` (float): The initial parameter 'a'. Determines step size to update parameters in SPSA (only used for SPSA). 191 | - `spsa_c1` (float): The initial parameter 'c'. The step size used to approximate gradient in SPSA (only used for SPSA). 192 | - `max_evals_grouped` (int): Maximum number of evaluations performed simultaneously. 193 | - `rep_delay` (float): Delay between programs in seconds. 194 | - `shots` (int): The total number of shots for the simulation (overwritten for the statevector backend). 195 | - `fix_first_bitstring` (bool): Bypasses computation of first bitstring and replaces result with HF energy. This setting assumes that the first bitstring is the HF state (e.g. [1,1,1,1,0,0,0]). Can speed up the computation, but requires ansatz that leaves the HF state unchanged under var_form. 196 | - `bootstrap_trials` (int): A setting for generating error bars (not used for the statevector backend). 197 | - `copysample_job_size` (int or NoneType): A setting to approximately realize weighted sampling of circuits according to their relative significance 198 | (Schmidt coefficients). This number should be bigger than the number of unique circuits running (not used for the statevector backend). 199 | - `meas_error_mit` (bool): Performs measurement error mitigation (not used for the statevector backend). 200 | - `meas_error_shots` (int): The number of shots for measurement error mitigation (not used for the statevector backend). 201 | - `meas_error_refresh_period_minutes` (float): How often to refresh the calibration 202 | matrix in measurement error mitigation, in minutes (not used for the statevector backend). 203 | - `zero_noise_extrap` (bool): Linear extrapolation for gate error mitigation (ignored for the statevector backend) 204 | 205 | ### Specifying the converter 206 | The qubit converter is specified as: 207 | ``` 208 | converter = QubitConverter(JordanWignerMapper()) 209 | ``` 210 | 211 | ### The solver 212 | `EntanglementForgedGroundStateSolver` is the ground state calculation interface for entanglement forging. It is specified as follows: 213 | ``` 214 | forged_ground_state_solver = EntanglementForgedGroundStateSolver(converter, ansatz, bitstrings, config) 215 | ``` 216 | 217 | ### Running the algorithm 218 | ``` 219 | forged_result = forged_ground_state_solver.solve(problem) 220 | ``` 221 | 222 | ### Viewing the results 223 | The results can be viewed by calling `forged_result`: 224 | ``` 225 | >>> forged_result 226 | Ground state energy (Hartree): -1.1372604047327592 227 | Schmidt values: [-0.99377311 0.11142266] 228 | Optimizer parameters: [ 6.2830331 -18.77969966] 229 | ``` 230 | 231 | ### Verbose 232 | To activate verbose output 233 | ``` 234 | from entanglement_forging import Log 235 | Log.VERBOSE = True 236 | ``` 237 | 238 | 239 | ## Troubleshooting 240 | 241 | ### Getting poor results on the hardware 242 | Try using `fix_first_bitstring=True` in `EntanglementForgedConfig`. This bypasses the computation of the first bitstring and replaces the result with the HF energy. This setting requires an ansatz that leaves the HF state unchanged under `var_form`. 243 | 244 | ### For IBM Power users 245 | pip is not well-supported on IBM power, so everything should be installed with conda. To get the package to work on Power, one needs to: 246 | - remove `matplotlib>=2.1,<3.4` from setup.cfg 247 | - install matplotlib with conda manually instead of pip 248 | ``` 249 | pip uninstall matplotlib 250 | conda install matplotlib 251 | ``` 252 | -------------------------------------------------------------------------------- /docs/3-explanatory_material/explanatory_material.md: -------------------------------------------------------------------------------- 1 | # Explanatory material for the entanglement forging module 2 | 3 | ## Table of Contents 4 | 1. [Overview of entanglement forging](#overview-of-entanglement-forging) 5 | 2. [Entanglement Forging Procedure](#entanglement-forging-procedure) 6 | 3. [Scaling](#scaling) 7 | - [Freezing orbitals](#freezing-orbitals) 8 | - [Picking the bitstrings](#picking-the-bitstrings) 9 | - [Designing the ansatz used in Entanglement Forging](#designing-the-ansatz-used-in-entanglement-forging) 10 | - [Picking the backend](#picking-the-backend) 11 | 4. [⚠️ Current limitations](#%EF%B8%8F-current-limitations) 12 | - [Ansatz & bitstrings](#ansatz--bitstrings) 13 | - [Orbitals](#orbitals) 14 | - [Converter](#converter) 15 | - [Running on quantum hardware](#running-on-quantum-hardware) 16 | - [Unsupported Qiskit VQE features](#unsupported-qiskit-vqe-features) 17 | 5. [References](#references) 18 | 19 | 20 | ## Overview of entanglement forging 21 | Entanglement forging [1] was introduced as a way to reduce the number of qubits necessary to perform quantum simulation of chemical or physical systems. In general, to simulate *n* orbitals in a chemistry problem, one typically needs 2*n* qubits. Entanglement Forging makes it possible to represent expectation values of a 2*n*-qubit wavefunction as sums of multiple expectation values of *n*-qubit states, embedded in a classical computation, thus doubling the size of the system that can be *exactly* simulated with a fixed number of qubits. Furthermore, Entanglement Forging permits the circuits necessary for the *n*-qubit simulations to be shallower, relaxing requirements on gate error and connectivity, at the cost of increased quantum and classical run times. 22 | 23 | Previous techniques for reducing qubit count in quantum simulation applications could either reduce qubits slightly at the expense of deeper circuits (e.g. 2-qubit reduction, tapering), or yield a 50% qubit reduction at the expense of lower accuracy (e.g. restricted simulations). Using Entanglement Forging, one can achieve a 50% reduction in the number of qubits without compromising accuracy. 24 | 25 | The underlying idea which enables Entanglement Forging is that a quantum system on 2*n* qubits can be partitioned into 2 subsystems, and that a Schmidt decomposition of the 2*n*-qubit wavefunction with respect to those subsystems is possible. Because of this decomposition, we obtain an accurate classical representation of the entanglement between the two subsystems. 26 | 27 | The schematic below outlines how the expectation value *M* of a 2*n*-qubit wavefunction |ψ>2*n* with respect to a 2*n*-qubit Hamiltonian H2*n* can be decomposed into a sum of expectation values of products of *n*-qubit wavefunctions with respect to *n*-qubit operators. These *n*-qubit expectation values correspond to sub-experiments. 28 | 29 | ![Entanglement Forging Infographic](figs/forging_info_graphic.png) 30 | 31 | 32 | ## Entanglement Forging Procedure 33 | Entanglement Forging leverages near-term, heuristic algorithms, such as VQE, to provide an estimate of the 2*n*-qubit expectation value. It does so by assuming a parameterized ansatz for the wavefunction of each sub-system. (Note that the parameters of this ansatz describe the unitaries *U* and *V* in the Schmidt decomposition.) After the expectation value *M* has been decomposed into sub-experiments, the procedure is as follows: 34 | 1. Execute each sub-experiment on the QPU a number of times necessary to obtain sufficient statistics. 35 | 2. Combine the expectation values for the sub-experiments with the weights *w*a,b and the Schmidt parameters *λ*n to obtain an estimate for *M*. 36 | 3. Send the estimate of *M*, along with *λ*n and the variational parameters {θ} describing *U* and *V*, to a classical optimizer. 37 | 4. Use the classical optimizer to further minimize *M* and provide a new set for the variational parameters {θ} and Schmidt coefficients *λ*n. 38 | 5. Update the sub-experiments based on the updated {θ} and *λ*n. 39 | 6. Repeat Steps 1-5 until the estimate for *M* converges. 40 | 41 | Note that if *M* is the expectation value of the system's Hamiltonian, then it is possible to separate the optimization over the variational parameters {θ} and the Schmidt coefficients *λ*n. In particular, the Schmidt coefficients can be optimized after step 2, and separately from the variational parameters. 42 | 43 | Further, an easy way to reduce the number of sub-experiments necessary is by truncating the Schmidt decomposition of |ψ> to include only some number of the bitstring states |*b*n>. However, doing so will generally lead to less accuracy in the estimation of the expectation value. 44 | 45 | 46 | ## Scaling 47 | The execution time scales differently with various properties of the simulations, and is indicated in the table below. 48 | 49 | | Quantity | Scaling | Notes | Ways to Reduce | 50 | | --- | ---- | --- | --- | 51 | | Orbitals | Fifth power | | [Orbital freezing](#freezing-orbitals) | 52 | | Bitstring states \|*b*n>| Quadratic| Increasing the number of bitstring states can increase the accuracy of the simulation, but at the expense of execution time. | [Schmidt decomposition truncation](#picking-the-bitstrings) | 53 | | Ansatz parameters {θ} | Linear |An increased number of ansatz parameters can increase the accuracy of the simulation, but at the expense of execution time. | [Redesign the ansatz](#designing-the-ansatz-used-in-entanglement-forging) | 54 | 55 | ### Freezing orbitals 56 | Since the execution time scales with the 5th power in the number of orbitals, it's a good idea to simplify the problem (if possible) by eliminating some of the orbitals. Some knowledge of chemistry is useful when picking orbitals to freeze. One good rule of thumb is to freeze the core orbital (for the case of water, this is the core oxygen 1s orbital). Furthermore, in the case of water, it turns out that orbital 3 (corresponding to the out-of-plane oxygen 2p orbitals) has different symmetry to the other orbitals, so excitations to orbital 3 are suppressed. For water, we thus freeze orbitals 0 and 3. 57 | 58 | The core orbitals can be found with the following code: 59 | ``` 60 | qmolecule = driver.run() 61 | print(f"Core orbitals: {qmolecule.core_orbitals}") 62 | ``` 63 | 64 | #### Example: Water molecule 65 | The total number of orbitals (core + valence) = 7 orbitals 66 | 67 | Frozen orbital approximation = 2 orbitals 68 | 69 | Active space orbitals = total number of orbitals – frozen orbitals = 5 orbitals (bitstring size is set to 5) 70 | 71 | Leading excitation analysis = 3 unique bitstrings 72 | 73 | ```python 74 | >>> from entanglement_forging import reduce_bitstrings 75 | >>> orbitals_to_reduce = [0,3] 76 | >>> bitstrings = [[1,1,1,1,1,0,0],[1,0,1,1,1,0,1],[1,0,1,1,1,1,0]] 77 | >>> reduced_bitstrings = reduce_bitstrings(bitstrings, orbitals_to_reduce) 78 | >>> print(f'Bitstrings after orbital reduction: {reduced_bitstrings}') 79 | Bitstrings after orbital reduction: [[1, 1, 1, 0, 0], [0, 1, 1, 0, 1], [0, 1, 1, 1, 0]] 80 | ``` 81 | 82 | ### Picking the bitstrings 83 | 84 | #### General Considerations 85 | Picking appropriate bitstrings requires prior knowledge of the molecular electronic structure. 86 | 87 | In general, the exact electronic wavefunction is a superposition of all possible distributions of the ~N~ electrons over the ~L~ orbitals and is exponential in size. However, only a relatively small number of excitations contribute significantly to the correlation energy. By identifying such leading electronic excitations, a linear combination of electronic configurations/Slater determinants that capture the most important portion of the Hilbert space and make the biggest contribution to the electronic wavefunction description can be selected. This allows for reduction in computational resources. 88 | 89 | The leading electronic excitations can be represented in standard bitstrings (e.g. `[1,1,1,1,0,0,0]`). When an orbital is occupied by a spin up (α electron) or spin down (β electron), its bit will be set to 1. Therefore: 90 | - the number of bits in each bitstring should be equal the number of spatial orbitals 91 | - the number of 1s in each bitstring should equal the number of α or β particles. 92 | 93 | Further reduction in computational resources can be achieved by [freezing some orbitals](./docs/4-explanatory_material/explanatory_material.md#freezing-orbitals) that do not participate in electronic excitations (i.e. core orbitals or those that lie out of symmetry) by removing the bits that correspond to them. 94 | 95 | #### Fixing the Hartree-Fock bitstring 96 | In some cases, it is possible to increase the accuracy of simulations and speed up the execution by setting `fix_first_bitstring=True` in `EntanglementForgedConfig`. This bypasses the computation of the first bitstring and replaces the result with HF energy. 97 | 98 | This setting requires an ansatz that leaves the Hartree-Fock (HF) state unchanged under `var_form`. As a rule of thumb, this can be achieved by restricting entanglement between the qubits representing occupied orbitals (bits = 1) in the HF state and the qubits representing unoccupied orbitals (bits = 0) in the HF state. 99 | 100 | For example, this figure from [1] shows the A, B, and C qubits entangled with the hop gates, D & E qubits entangled with hop gates, while the partition between (A,B,C) and (D,E) are only entangled with a CZ gate. 101 | 102 | 103 | 104 | ### Designing the ansatz used in Entanglement Forging 105 | Because entanglement forging leverages a near-term, heuristic algorithm (namely, VQE), a judicious choice for the VQE ansatz can improve performance. Note that one way to design the ansatz is by endowing the unitaries *U* and *V* in the Schmidt decomposition with parameters. An open question is how to choose the best unitaries for a given problem. 106 | 107 | For a chemistry simulation problem, the number of qubits in the circuit must equal the number of orbitals (minus the number of frozen orbitals, if applicable). 108 | 109 | ### Picking the backend 110 | `backend` is an option in the [`EntanglementForgedConfig`](https://github.com/qiskit-community/prototype-entanglement-forging/blob/main/docs/2-reference_guide/reference_guide.md#options-entanglementforgedconfig) class. Users can choose between Statevector simulation, QASM simulation, or real quantum hardware. 111 | 112 | Statevector simulation is useful when we want to: 113 | 1. get the exact values of energies (e.g. for chemistry problems) without any error bars (assuming there are no other sources of randomness) 114 | 2. test the performance of an algorithm in the absence of shot noise (for VQE, there could be a difference between the trajectory of the parameters in the presence and absence of shot noise; in this case the statevector simulator can concretely provide an answer regarding the expressivity of a given ansatz without any uncertainty coming from shot noise) 115 | 116 | QASM simulation is useful when: 117 | 1. the system sizes are larger because the statevector simulator scales exponentially in system size and will not be useful beyond small systems 118 | 2. simulating circuits with noise to mimic a real noisy quantum computer 119 | 120 | When running the entanglement forging module either on the QASM simulator or on real quantum hardware, several additional options are available: `shots`, `bootstrap_trials`, `copysample_job_size`, `meas_error_mit`, `meas_error_shots`, `meas_error_refresh_period_minutes`, `zero_noise_extrap`. These options can be specified in the [`EntanglementForgedConfig`](https://github.com/qiskit-community/prototype-entanglement-forging/blob/main/docs/2-reference_guide/reference_guide.md#options-entanglementforgedconfig) class. Users can use the QASM simulator to test out these options before running them on real quantum hardware. 121 | 122 | Notes: 123 | - In the limit of infinite shots, the mean value of the QASM simulator will be equal to the value of the statevector simulator. 124 | - The QASM simulator also has a method that [mimics the statevector simulator](https://qiskit.org/documentation/tutorials/simulators/1_aer_provider.html) without shot noise as an alternative to statevector simulator. 125 | 126 | 127 | ## ⚠️ Current limitations 128 | 129 | ### Ansatz & bitstrings 130 | - It is currently an open problem how to pick the best circuit (ansatze) for VQE (and thus Entanglement Forging) for a given system. 131 | - It is also currently an open problem how to pick the best bitstring for Entanglement Forging. 132 | - In the current implementation of the module, the same ansatz circuit is used for both spin-up and spin-down systems, U and V. 133 | - In the current implementation of the module, the ansatz must be real. 134 | - For molecular calculations, one can usually force the ansatz to be real. On the other hand, in crystalline solids (away from the gamma point and without inversion symmetry), the Hamiltonian is defined by the complex numbers. 135 | - There are plans in the future to implement complex ansatze. 136 | 137 | ### Orbitals 138 | - The current implementation of Forged VQE also requires that the number of alpha particles equals the number of beta particles. The relevant parameters can be found with the following code: 139 | ``` 140 | qmolecule = driver.run() 141 | print(f"Number of spatial orbitals: {qmolecule.num_molecular_orbitals}") 142 | print(f"Number of alpha particles: {qmolecule.num_alpha}") 143 | print(f"Number of beta particles: {qmolecule.num_beta}") 144 | ``` 145 | 146 | ### Converter 147 | - The current implementation only supports the `JordanWignerMapper` converter. 148 | 149 | ### Results 150 | - In the current implementation, only the energy of the final state is available. It would be useful to have a feature to output the 1- and 2-body density matrices of the final state after the optimization. 151 | - The 1-body matrices are used for: 152 | - electrostatic properties 153 | - electronic densities 154 | - molecular electrostatic potential 155 | - 2-body matrices are used for: 156 | - orbital optimization 157 | - analysis of correlation functions 158 | - The combination of both is used in entanglement analysis. 159 | 160 | ### Running on quantum hardware 161 | - Results on hardware will not be as good as on the QASM simulator. Getting good results will require using a quantum backend with good properties (qubit fidelity, gate fidelity etc.), as well as a lot of fine-tuning of parameters. 162 | - Queue times are long, which makes execution on a quantum backend difficult. This issue will be mitigated once the module can be uploaded as a qiskit runtime (not currently supported). This will be made possible when either: 163 | - The module has been simplified to fit into a single program, or 164 | - Qiskit runtime provides support for multi-file programs 165 | 166 | ### Unsupported Qiskit VQE features 167 | This module is based on Qiskit's Variational Quantum Eigensolver (VQE) algorithm, however, some of the features available in VQE are not currently supported by this module. Here is a list of known features that are not supported: 168 | 169 | - Using `QuantumInstance` instead of `backend`. 170 | 171 | This list is not exhaustive. 172 | 173 | 174 | ## References 175 | This module is based on the theory and experiment described in the following paper: 176 | 177 | [1] Andrew Eddins, Mario Motta, Tanvi P. Gujarati, Sergey Bravyi, Antonio Mezzacapo, Charles Hadfield, Sarah Sheldon, *Doubling the size of quantum simulators by entanglement forging*, https://arxiv.org/abs/2104.10220 178 | -------------------------------------------------------------------------------- /docs/3-explanatory_material/figs/Fig_5_c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/prototype-entanglement-forging/c10fa9421b23412d5e0bee3632f0c15cb3d45764/docs/3-explanatory_material/figs/Fig_5_c.png -------------------------------------------------------------------------------- /docs/3-explanatory_material/figs/forging_info_graphic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/prototype-entanglement-forging/c10fa9421b23412d5e0bee3632f0c15cb3d45764/docs/3-explanatory_material/figs/forging_info_graphic.png -------------------------------------------------------------------------------- /docs/images/ef_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/prototype-entanglement-forging/c10fa9421b23412d5e0bee3632f0c15cb3d45764/docs/images/ef_image.png -------------------------------------------------------------------------------- /entanglement_forging/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """Entanglement forging docstring.""" 14 | 15 | from .core.entanglement_forged_config import EntanglementForgedConfig 16 | from .core.orbitals_to_reduce import OrbitalsToReduce 17 | from .core.wrappers.entanglement_forged_vqe import EntanglementForgedVQE 18 | from .core.wrappers.entanglement_forged_ground_state_eigensolver import ( 19 | EntanglementForgedGroundStateSolver, 20 | ) 21 | from .core.wrappers.entanglement_forged_driver import EntanglementForgedDriver 22 | from .utils.generic_execution_subroutines import reduce_bitstrings 23 | from .utils.log import Log 24 | -------------------------------------------------------------------------------- /entanglement_forging/core/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | -------------------------------------------------------------------------------- /entanglement_forging/core/cholesky_hamiltonian.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """Cholesky hamiltonian module.""" 14 | 15 | import numpy as np 16 | from qiskit_nature.converters.second_quantization import QubitConverter 17 | from qiskit_nature.mappers.second_quantization import JordanWignerMapper 18 | from qiskit_nature.properties.second_quantization.electronic.integrals import ( 19 | IntegralProperty, 20 | OneBodyElectronicIntegrals, 21 | TwoBodyElectronicIntegrals, 22 | ) 23 | from qiskit_nature.properties.second_quantization.electronic.bases import ( 24 | ElectronicBasis, 25 | ) 26 | 27 | from ..utils.log import Log 28 | 29 | 30 | # pylint: disable=invalid-name 31 | def modified_cholesky(two_body_overlap_integrals, eps): 32 | """Performs modified Cholesky decomposition ("mcd") on array 33 | two_body_overlap_integrals with threshold eps. 34 | 35 | H= H_1 \\otimes I + I \\otimes H_1 + \\sum_\\gamma L_\\gamma \\otimes L_\\gamma 36 | 37 | Parameters: 38 | two_body_overlap_integrals (np.ndarray): A 4D array containing values of all 39 | 2-body h2 overlap integrals 40 | eps (float): The threshold for the decomposition (typically a number close to 0) 41 | 42 | Returns: 43 | A tuple containing: 44 | n_gammas (int): the number of Cholesky matrices L_\\gamma 45 | L (numpy.ndarray): a 3D array containing the set of Cholesky matrices L_\\gamma 46 | """ 47 | n_basis_states = two_body_overlap_integrals.shape[0] # number of basis states 48 | # max number of Cholesky vectors, and current number of Cholesky vectors 49 | # (n_gammas = "number of gammas") 50 | chmax, n_gammas = 10 * n_basis_states, 0 51 | W = two_body_overlap_integrals.reshape(n_basis_states**2, n_basis_states**2) 52 | L = np.zeros((n_basis_states**2, chmax)) 53 | Dmax = np.diagonal(W).copy() 54 | nu_max = np.argmax(Dmax) 55 | vmax = Dmax[nu_max] 56 | while vmax > eps: 57 | L[:, n_gammas] = W[:, nu_max] 58 | if n_gammas > 0: 59 | L[:, n_gammas] -= np.dot(L[:, 0:n_gammas], L.T[0:n_gammas, nu_max]) 60 | L[:, n_gammas] /= np.sqrt(vmax) 61 | Dmax[: n_basis_states**2] -= L[: n_basis_states**2, n_gammas] ** 2 62 | n_gammas += 1 63 | nu_max = np.argmax(Dmax) 64 | vmax = Dmax[nu_max] 65 | L = L[:, :n_gammas].reshape((n_basis_states, n_basis_states, n_gammas)) 66 | return n_gammas, L 67 | 68 | 69 | def get_fermionic_ops_with_cholesky( 70 | # pylint: disable=too-many-arguments disable-msg=too-many-locals disable=too-many-branches disable=too-many-statements 71 | mo_coeff, 72 | h1, 73 | h2, 74 | opname, 75 | halve_transformed_h2=False, 76 | occupied_orbitals_to_reduce=None, 77 | virtual_orbitals_to_reduce=None, 78 | epsilon_cholesky=1e-10, 79 | verbose=False, 80 | ): 81 | """Decomposes the Hamiltonian operators into a form appropriate for entanglement forging. 82 | 83 | Parameters: 84 | mo_coeff (np.ndarray): 2D array representing coefficients for converting from AO to MO basis. 85 | h1 (np.ndarray): 2D array representing operator 86 | coefficients of one-body integrals in the AO basis. 87 | h2 (np.ndarray or None): 4D array representing operator coefficients 88 | of two-body integrals in the AO basis. 89 | halve_transformed_h2 (Bool): Optional; Should be set to True for Hamiltonian 90 | operator to agree with Qiskit conventions, apparently. 91 | occupied_orbitals_to_reduce (list): Optional; A list of occupied orbitals that will be removed. 92 | virtual_orbitals_to_reduce (list):Optional; A list of virtual orbitals that will be removed. 93 | epsilon_cholesky (float): Optional; The threshold for the decomposition 94 | (typically a number close to 0). 95 | verbose (Bool): Optional; Option to print various output during computation. 96 | 97 | Returns: 98 | qubit_op (WeightedPauliOperator): H_1 in the Cholesky decomposition. 99 | cholesky_ops (list of WeightedPauliOperator): L_\\gamma in the Cholesky decomposition 100 | freeze_shift (float): Energy shift due to freezing. 101 | h1 (numpy.ndarray): 2D array representing operator coefficients of one-body 102 | integrals in the MO basis. 103 | h2 (numpy.ndarray or None): 4D array representing operator coefficients of 104 | two-body integrals in the MO basis. 105 | """ 106 | if virtual_orbitals_to_reduce is None: 107 | virtual_orbitals_to_reduce = [] 108 | if occupied_orbitals_to_reduce is None: 109 | occupied_orbitals_to_reduce = [] 110 | C = mo_coeff 111 | del mo_coeff 112 | h1 = np.einsum("pi,pr->ir", C, h1) 113 | h1 = np.einsum("rj,ir->ij", C, h1) # h_{pq} in MO basis 114 | 115 | # do the Cholesky decomposition: 116 | if h2 is not None: 117 | ng, L = modified_cholesky(h2, epsilon_cholesky) 118 | if verbose: 119 | h2_mcd = np.einsum("prg,qsg->prqs", L, L) 120 | Log.log("mcd threshold =", epsilon_cholesky) 121 | Log.log( 122 | "deviation between mcd and original eri =", np.abs(h2_mcd - h2).max() 123 | ) 124 | Log.log("number of Cholesky vectors =", ng) 125 | Log.log("L.shape = ", L.shape) 126 | del h2_mcd 127 | # obtain the L_{pr,g} in the MO basis 128 | L = np.einsum("prg,pi,rj->ijg", L, C, C) 129 | else: 130 | size = len(h1) 131 | ng, L = 0, np.zeros(shape=(size, size, 0)) 132 | 133 | if occupied_orbitals_to_reduce is None: 134 | occupied_orbitals_to_reduce = [] 135 | 136 | if virtual_orbitals_to_reduce is None: 137 | virtual_orbitals_to_reduce = [] 138 | 139 | if len(occupied_orbitals_to_reduce) > 0: 140 | if verbose: 141 | Log.log("Reducing occupied orbitals:", occupied_orbitals_to_reduce) 142 | orbitals_not_to_reduce = list( 143 | sorted(set(range(len(h1))) - set(occupied_orbitals_to_reduce)) 144 | ) 145 | 146 | h1_frozenpart = h1[ 147 | np.ix_(occupied_orbitals_to_reduce, occupied_orbitals_to_reduce) 148 | ] 149 | h1_activepart = h1[np.ix_(orbitals_not_to_reduce, orbitals_not_to_reduce)] 150 | L_frozenpart = L[ 151 | np.ix_(occupied_orbitals_to_reduce, occupied_orbitals_to_reduce) 152 | ] 153 | L_activepart = L[np.ix_(orbitals_not_to_reduce, orbitals_not_to_reduce)] 154 | 155 | freeze_shift = ( 156 | 2 * np.einsum("pp", h1_frozenpart) 157 | + 2 * np.einsum("ppg,qqg", L_frozenpart, L_frozenpart) 158 | - np.einsum("pqg,qpg", L_frozenpart, L_frozenpart) 159 | ) 160 | h1 = ( 161 | h1_activepart 162 | + 2 * np.einsum("ppg,qsg->qs", L_frozenpart, L_activepart) 163 | - np.einsum( 164 | "psg,qpg->qs", 165 | L[np.ix_(occupied_orbitals_to_reduce, orbitals_not_to_reduce)], 166 | L[np.ix_(orbitals_not_to_reduce, occupied_orbitals_to_reduce)], 167 | ) 168 | ) 169 | L = L_activepart 170 | else: 171 | freeze_shift = 0 172 | 173 | if len(virtual_orbitals_to_reduce) > 0: 174 | if verbose: 175 | Log.log("Reducing virtual orbitals:", virtual_orbitals_to_reduce) 176 | virtual_orbitals_to_reduce = np.asarray(virtual_orbitals_to_reduce) 177 | virtual_orbitals_to_reduce -= len(occupied_orbitals_to_reduce) 178 | orbitals_not_to_reduce = list( 179 | sorted(set(range(len(h1))) - set(virtual_orbitals_to_reduce)) 180 | ) 181 | h1 = h1[np.ix_(orbitals_not_to_reduce, orbitals_not_to_reduce)] 182 | L = L[np.ix_(orbitals_not_to_reduce, orbitals_not_to_reduce)] 183 | else: 184 | pass 185 | 186 | h2 = np.einsum("prg,qsg->prqs", L, L) 187 | if halve_transformed_h2: 188 | h2 /= 2 189 | h1_int = OneBodyElectronicIntegrals(basis=ElectronicBasis.SO, matrices=h1) 190 | h2_int = TwoBodyElectronicIntegrals(basis=ElectronicBasis.SO, matrices=h2) 191 | int_property = IntegralProperty("fer_op", [h1_int, h2_int]) 192 | 193 | if isinstance(int_property.second_q_ops(), dict): 194 | fer_op = int_property.second_q_ops()["fer_op"] 195 | else: 196 | fer_op = int_property.second_q_ops()[0] 197 | 198 | converter = QubitConverter(JordanWignerMapper()) 199 | qubit_op = converter.convert(fer_op) 200 | # pylint: disable=protected-access 201 | qubit_op._name = opname + "_onebodyop" 202 | cholesky_ops = [] 203 | for g in range(L.shape[2]): 204 | cholesky_int = OneBodyElectronicIntegrals( 205 | basis=ElectronicBasis.SO, matrices=L[:, :, g] 206 | ) 207 | cholesky_property = IntegralProperty("cholesky_op", [cholesky_int]) 208 | if isinstance(cholesky_property.second_q_ops(), dict): 209 | cholesky_op = converter.convert( 210 | cholesky_property.second_q_ops()["cholesky_op"] 211 | ) 212 | else: 213 | cholesky_op = converter.convert(cholesky_property.second_q_ops()[0]) 214 | # pylint: disable=protected-access 215 | cholesky_op._name = opname + "_chol" + str(g) 216 | cholesky_ops.append(cholesky_op) 217 | return qubit_op, cholesky_ops, freeze_shift, h1, h2 218 | -------------------------------------------------------------------------------- /entanglement_forging/core/classical_energies.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """Classical energies.""" 14 | # pylint: disable=duplicate-code 15 | 16 | import numpy as np 17 | from pyscf import gto, scf, mp, ao2mo, fci 18 | 19 | from qiskit_nature.problems.second_quantization import ElectronicStructureProblem 20 | from qiskit_nature.properties.second_quantization.electronic.bases import ( 21 | ElectronicBasis, 22 | ) 23 | from qiskit_nature.drivers.second_quantization import ElectronicStructureDriver 24 | 25 | from entanglement_forging.core.cholesky_hamiltonian import ( 26 | get_fermionic_ops_with_cholesky, 27 | ) 28 | from entanglement_forging.core.orbitals_to_reduce import OrbitalsToReduce 29 | 30 | 31 | # pylint: disable=invalid-name 32 | class ClassicalEnergies: # pylint: disable=too-many-instance-attributes disable=too-few-public-methods 33 | """Runs classical energy computations. 34 | 35 | Attributes: 36 | HF (float) = Hartree Fock energy 37 | MP2 (float) = MP2 Energy 38 | FCI (float) = Full Configuration Interaction energy 39 | shift (float) = The sum of the nuclear repulsion energy 40 | and the energy shift due to orbital freezing 41 | """ 42 | 43 | def __init__(self, problem: ElectronicStructureProblem, all_orbitals_to_reduce): 44 | """Initialize the classical energies. 45 | Args: 46 | qmolecule (ElectronicStructureProblem): Problem class containing molecule driver 47 | all_to_reduce (entanglement_forging.core.orbitals_to_reduce.OrbitalsToReduce): 48 | All orbitals to be reduced. 49 | """ 50 | # pylint: disable=too-many-locals disable=too-many-statements 51 | self.problem = problem 52 | 53 | self.all_orbitals_to_reduce = all_orbitals_to_reduce 54 | self.orbitals_to_reduce = OrbitalsToReduce(self.all_orbitals_to_reduce, problem) 55 | self.epsilon_cholesky = 1e-10 56 | 57 | if isinstance(problem.driver, ElectronicStructureDriver): 58 | particle_number = self.problem.grouped_property.get_property( 59 | "ParticleNumber" 60 | ) 61 | electronic_basis_transform = self.problem.grouped_property.get_property( 62 | "ElectronicBasisTransform" 63 | ) 64 | electronic_energy = self.problem.grouped_property.get_property( 65 | "ElectronicEnergy" 66 | ) 67 | 68 | num_alpha = particle_number.num_alpha 69 | num_beta = particle_number.num_beta 70 | mo_coeff = electronic_basis_transform.coeff_alpha 71 | hcore = electronic_energy.get_electronic_integral( 72 | ElectronicBasis.AO, 1 73 | )._matrices[0] 74 | eri = electronic_energy.get_electronic_integral( 75 | ElectronicBasis.AO, 2 76 | )._matrices[0] 77 | 78 | num_molecular_orbitals = mo_coeff.shape[0] 79 | 80 | nuclear_repulsion_energy = electronic_energy.nuclear_repulsion_energy 81 | 82 | else: 83 | hcore = problem.driver._hcore 84 | mo_coeff = problem.driver._mo_coeff 85 | eri = problem.driver._eri 86 | num_alpha = problem.driver._num_alpha 87 | num_beta = problem.driver._num_beta 88 | nuclear_repulsion_energy = problem.driver._nuclear_repulsion_energy 89 | num_molecular_orbitals = mo_coeff.shape[0] 90 | 91 | n_electrons = num_molecular_orbitals - len(self.orbitals_to_reduce.all) 92 | 93 | n_alpha_electrons = num_alpha - len(self.orbitals_to_reduce.occupied()) 94 | n_beta_electrons = num_beta - len(self.orbitals_to_reduce.occupied()) 95 | 96 | fermionic_op = get_fermionic_ops_with_cholesky( 97 | mo_coeff, 98 | hcore, 99 | eri, 100 | opname="H", 101 | halve_transformed_h2=True, 102 | occupied_orbitals_to_reduce=self.orbitals_to_reduce.occupied(), 103 | virtual_orbitals_to_reduce=self.orbitals_to_reduce.virtual(), 104 | epsilon_cholesky=self.epsilon_cholesky, 105 | ) 106 | # hi - 2D array representing operator coefficients of one-body integrals in the AO basis. 107 | _, _, freeze_shift, h1, h2 = fermionic_op 108 | Enuc = freeze_shift + nuclear_repulsion_energy 109 | # 4D array representing operator coefficients of two-body integrals in the AO basis. 110 | h2 = 2 * h2 111 | mol_FC = gto.M(verbose=0) 112 | mol_FC.charge = 0 113 | mol_FC.nelectron = n_alpha_electrons + n_beta_electrons 114 | mol_FC.spin = n_alpha_electrons - n_beta_electrons 115 | mol_FC.incore_anyway = True 116 | mf_FC = scf.RHF(mol_FC) 117 | mf_FC.get_hcore = lambda *args: h1 118 | mf_FC.get_ovlp = lambda *args: np.eye(n_electrons) 119 | 120 | mf_FC._eri = ao2mo.restore(8, h2, n_electrons) 121 | rho = np.zeros((n_electrons, n_electrons)) 122 | for i in range(n_alpha_electrons): 123 | rho[i, i] = 2.0 124 | 125 | Ehf = mf_FC.kernel(rho) 126 | ci_FC = fci.FCI(mf_FC) 127 | Emp = mp.MP2(mf_FC).kernel()[0] 128 | Efci, _ = ci_FC.kernel() 129 | 130 | self.HF = Ehf 131 | self.MP2 = Ehf + Emp 132 | self.FCI = Efci 133 | self.shift = Enuc 134 | -------------------------------------------------------------------------------- /entanglement_forging/core/entanglement_forged_config.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """Class for configuration settings.""" 14 | 15 | import numpy as np 16 | 17 | from entanglement_forging.utils.log import Log 18 | 19 | 20 | # pylint: disable=too-many-instance-attributes,too-many-arguments,too-many-locals,too-few-public-methods 21 | class EntanglementForgedConfig: 22 | """Class for configuration settings. 23 | 24 | Attr: 25 | backend (Union[Backend, BaseBackend]): Instance of selected backend. 26 | qubit_layout (NoneType|qiskit.transpiler.Layout|dict|list): Initial position of virtual 27 | qubits on physical qubits. If this layout makes the circuit compatible with the 28 | coupling_map constraints, it will be used. 29 | initial_params (NoneType or list of int): A list specifying the initial optimization 30 | parameters. 31 | maxiter (int): Maximum number of optimizer iterations to perform. 32 | optimizer_name (str): e.g. 'SPSA', 'ADAM', 'NELDER_MEAD', 'COBYLA', 'L_BFGS_B', 'SLSQP' ... 33 | optimizer_tol' (float): Optimizer tolerance, e.g. 1e-6. 34 | skip_any_optimizer_cal (bool): Setting passed to any optimizer with a 'skip_calibration' 35 | argument. 36 | spsa_last_average (int): Number of times to average over final SPSA evaluations to 37 | determine optimal parameters (only used for SPSA). 38 | initial_spsa_iteration_idx (int): Iteration index to resume interrupted 39 | VQE run (only used for SPSA). 40 | spsa_c0 (float): The initial parameter 'a'. Determines step size to update 41 | parameters in SPSA (only used for SPSA). 42 | spsa_c1 (float): The initial parameter 'c'. The step size used to 43 | approximate gradient in SPSA (only used for SPSA). 44 | max_evals_grouped (int): Maximum number of evaluations performed simultaneously. 45 | rep_delay (float): Delay between programs in seconds. 46 | shots (int): The total number of shots for the simulation 47 | (overwritten for the statevector backend). 48 | fix_first_bitstring (bool): Bypasses computation of first bitstring and replaces 49 | result with HF energy. Can speed up the computation, but requires ansatz 50 | that leaves the HF state unchanged under var_form. bootstrap_trials (int): 51 | A setting for generating error bars (not used for the statevector backend). 52 | copysample_job_size (int or NoneType): A setting to approximately realize weighted 53 | sampling of circuits according to their relative significance 54 | (Schmidt coefficients). This number should be bigger than the number 55 | of unique circuits running (not used for the statevector backend). 56 | meas_error_mit (bool): Performs measurement error mitigation 57 | (not used for the statevector backend). 58 | meas_error_shots (int): The number of shots for measurement error mitigation 59 | (not used for the statevector backend). 60 | meas_error_refresh_period_minutes (float): How often to refresh the calibration 61 | matrix in measurement error mitigation, in minutes 62 | (not used for the statevector backend). 63 | zero_noise_extrap (bool): Linear extrapolation for gate error mitigation 64 | (ignored for the statevector backend) 65 | """ 66 | 67 | def __init__( 68 | self, 69 | backend, 70 | qubit_layout=None, 71 | initial_params=None, 72 | maxiter=100, 73 | optimizer_name="SPSA", 74 | optimizer_tol=1e-6, 75 | skip_any_optimizer_cal=True, 76 | spsa_last_average=10, 77 | initial_spsa_iteration_idx=0, 78 | spsa_c0=2 * 2 * np.pi, 79 | spsa_c1=0.1, 80 | max_evals_grouped=99, 81 | rep_delay=None, 82 | shots=1024, 83 | fix_first_bitstring=False, 84 | copysample_job_size=None, 85 | meas_error_mit=False, 86 | meas_error_shots=None, 87 | meas_error_refresh_period_minutes=30, 88 | bootstrap_trials=None, 89 | zero_noise_extrap=False, 90 | ): 91 | """The constructor for the EntanglementForgedConfig class.""" 92 | statevector_sims = ["statevector_simulator", "aer_simulator_statevector"] 93 | self.backend = backend 94 | self.backend_name = self.backend.configuration().backend_name 95 | self.qubit_layout = qubit_layout 96 | self.initial_params = initial_params 97 | self.maxiter = maxiter 98 | self.optimizer_name = optimizer_name 99 | self.optimizer_tol = optimizer_tol 100 | self.skip_any_optimizer_cal = skip_any_optimizer_cal 101 | self.spsa_last_average = spsa_last_average 102 | self.initial_spsa_iteration_idx = initial_spsa_iteration_idx 103 | self.spsa_c0 = spsa_c0 104 | self.spsa_c1 = spsa_c1 105 | self.max_evals_grouped = max_evals_grouped 106 | 107 | self.rep_delay = None 108 | dynamic_reprate_enabled = getattr( 109 | self.backend.configuration(), "dynamic_reprate_enabled", False 110 | ) 111 | if dynamic_reprate_enabled: 112 | self.rep_delay = rep_delay if rep_delay else 100e-6 113 | else: 114 | self.rep_delay = None 115 | 116 | self.shots = shots if self.backend_name not in statevector_sims else 1 117 | self.fix_first_bitstring = fix_first_bitstring 118 | self.bootstrap_trials = bootstrap_trials 119 | self.copysample_job_size = ( 120 | copysample_job_size if self.backend_name not in statevector_sims else None 121 | ) 122 | self.meas_error_mit = meas_error_mit 123 | self.meas_error_shots = meas_error_shots 124 | self.meas_error_refresh_period_minutes = meas_error_refresh_period_minutes 125 | self.zero_noise_extrap = [1, 3] if zero_noise_extrap else [1] 126 | self.validate() 127 | 128 | def validate(self): 129 | """Validates the configuration settings.""" 130 | statevector_sims = ["statevector_simulator", "aer_simulator_statevector"] 131 | # TODO check this. I might have mixed up the error mitigations # pylint: disable=fixme 132 | if self.meas_error_mit and self.zero_noise_extrap is None: 133 | raise ValueError( 134 | "You have set meas_error_mit == True and must therefore " 135 | "specify zero_noise_extrap, e.g., zero_noise_extrap = [1,3]." 136 | ) 137 | 138 | if ( 139 | self.backend_name not in statevector_sims 140 | and self.backend_name != "qasm_simulator" 141 | and self.backend is None 142 | ): 143 | raise ValueError( 144 | "You are using a real backend, so you must specify provider." 145 | ) 146 | 147 | if self.meas_error_mit and self.qubit_layout is None: 148 | raise ValueError( 149 | "If you would like to use measurement error mitigation, " 150 | "you must specify a qubit_layout." 151 | ) 152 | 153 | Log.log("Configuration settings are valid.") 154 | -------------------------------------------------------------------------------- /entanglement_forging/core/forged_operator.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """Forged operator.""" 14 | 15 | from typing import List 16 | 17 | import numpy as np 18 | from qiskit_nature.properties.second_quantization.electronic.bases import ( 19 | ElectronicBasis, 20 | ) 21 | from qiskit_nature.problems.second_quantization import ElectronicStructureProblem 22 | from qiskit_nature.drivers.second_quantization import ElectronicStructureDriver 23 | 24 | from .cholesky_hamiltonian import get_fermionic_ops_with_cholesky 25 | from .orbitals_to_reduce import OrbitalsToReduce 26 | from ..utils.log import Log 27 | 28 | 29 | # pylint: disable=too-many-locals,too-few-public-methods 30 | class ForgedOperator: 31 | """A class for the forged operator. 32 | 33 | Attributes: 34 | h_1_op (utils.legacy.weighted_pauli_operator.WeightedPauliOperator): 35 | TODO # pylint: disable=fixme 36 | h_chol_ops (list(utils.legacy.weighted_pauli_operator.WeightedPauliOperator)): 37 | TODO # pylint: disable=fixme 38 | 39 | E.g. a WeightedPauliOperator can be constructued as follows: 40 | WeightedPauliOperator([[(0.29994114732731), 41 | Pauli(z=[False, False, False], 42 | x=[False, False, False])], 43 | [(-0.13390761441581106), 44 | Pauli(z=[True, False, False], 45 | x=[False, False, False])]]]) 46 | """ 47 | 48 | def __init__( 49 | self, 50 | problem: ElectronicStructureProblem, 51 | all_orbitals_to_reduce: List[int], 52 | calculate_tensor_cross_terms: bool = False, 53 | ): 54 | self.problem = problem 55 | self.all_orbitals_to_reduce = all_orbitals_to_reduce 56 | self.orbitals_to_reduce = OrbitalsToReduce( 57 | self.all_orbitals_to_reduce, self.problem 58 | ) 59 | self.epsilon_cholesky = 1e-10 60 | self._calculate_tensor_cross_terms = calculate_tensor_cross_terms 61 | 62 | if isinstance(problem.driver, ElectronicStructureDriver): 63 | electronic_basis_transform = self.problem.grouped_property.get_property( 64 | "ElectronicBasisTransform" 65 | ) 66 | electronic_energy = self.problem.grouped_property.get_property( 67 | "ElectronicEnergy" 68 | ) 69 | particle_number = self.problem.grouped_property.get_property( 70 | "ParticleNumber" 71 | ) 72 | 73 | mo_coeff = electronic_basis_transform.coeff_alpha 74 | hcore = electronic_energy.get_electronic_integral( 75 | ElectronicBasis.AO, 1 76 | )._matrices[0] 77 | eri = electronic_energy.get_electronic_integral( 78 | ElectronicBasis.AO, 2 79 | )._matrices[0] 80 | num_alpha = particle_number.num_alpha 81 | num_beta = particle_number.num_beta 82 | 83 | else: 84 | mo_coeff = problem.driver._mo_coeff 85 | hcore = problem.driver._hcore 86 | eri = problem.driver._eri 87 | num_alpha = problem.driver._num_alpha 88 | num_beta = problem.driver._num_beta 89 | 90 | fermionic_results = get_fermionic_ops_with_cholesky( 91 | mo_coeff, 92 | hcore, 93 | eri, 94 | opname="H", 95 | halve_transformed_h2=True, 96 | occupied_orbitals_to_reduce=self.orbitals_to_reduce.occupied(), 97 | virtual_orbitals_to_reduce=self.orbitals_to_reduce.virtual(), 98 | epsilon_cholesky=self.epsilon_cholesky, 99 | ) 100 | self.h_1_op, self.h_chol_ops, _, _, _ = fermionic_results 101 | 102 | def construct(self): 103 | """Constructs the forged operator by extracting the Pauli operators and weights. 104 | 105 | The forged operator takes the form: Forged Operator = sum_ij w_ij T_ij + sum_ab w_ab S_ij, 106 | where w_ij and w_ab are coefficients, T_ij and S_ij are operators, and where the first term 107 | corresponds to the tensor product states while the second term corresponds to the 108 | superposition states. For more detail, refer to the paper 109 | TODO: add citation and equation ref 110 | 111 | Returns: 112 | tuple: a tuple containing: 113 | - tensor_paulis (list of str): e.g. ['III', 'IIZ', 'IXX', 'IYY', 'IZI', 114 | 'IZZ', 'XXI', 'XZX', 'YYI', 'YZY', 115 | 'ZII', 'ZIZ', 'ZZI'] 116 | - superpos_paulis (list of str): e.g. ['III', 'IIZ', 'IXX', 'IYY', 'IZI', 117 | 'XXI', 'XZX', 'YYI', 'YZY', 'ZII'] 118 | - w_ij (numpy.ndarray): 2D array 119 | - w_ab (numpy.ndarray): 2D array 120 | """ 121 | 122 | hamiltonian_ops = [self.h_1_op] 123 | if self.h_chol_ops is not None: 124 | for chol_op in self.h_chol_ops: 125 | hamiltonian_ops.append(chol_op) 126 | op1 = hamiltonian_ops[0] 127 | cholesky_ops = hamiltonian_ops[1:] 128 | # The block below calculate the Pauli-pair prefactors W_ij and returns 129 | # them as a dictionary 130 | tensor_paulis = set() 131 | superpos_paulis = set() 132 | paulis_each_op = [ 133 | { 134 | label: weight 135 | for label, weight in op.primitive.to_list() 136 | if np.abs(weight) > 0 137 | } 138 | for op in [op1] + list(cholesky_ops) 139 | ] 140 | paulis_each_op = [paulis_each_op[0]] + [p for p in paulis_each_op[1:] if p] 141 | for op_idx, paulis_this_op in enumerate(paulis_each_op): 142 | pnames = list(paulis_this_op.keys()) 143 | tensor_paulis.update(pnames) 144 | 145 | # Only pull bases from the single-body Hamiltonian to the 146 | # superposition basis if the calculate_tensor_cross_terms flag 147 | # is set 148 | if (not self._calculate_tensor_cross_terms) and (op_idx == 0): 149 | pass 150 | else: 151 | superpos_paulis.update(pnames) 152 | 153 | # ensure Identity string is represented since we will need it 154 | identity_string = "I" * len(pnames[0]) 155 | tensor_paulis.add(identity_string) 156 | Log.log("num paulis for tensor states:", len(tensor_paulis)) 157 | Log.log("num paulis for superpos states:", len(superpos_paulis)) 158 | tensor_paulis = list(sorted(tensor_paulis)) 159 | superpos_paulis = list(sorted(superpos_paulis)) 160 | pauli_ordering_for_tensor_states = { 161 | pname: idx for idx, pname in enumerate(tensor_paulis) 162 | } 163 | pauli_ordering_for_superpos_states = { 164 | pname: idx for idx, pname in enumerate(superpos_paulis) 165 | } 166 | w_ij = np.zeros((len(tensor_paulis), len(tensor_paulis))) 167 | w_ab = np.zeros((len(superpos_paulis), len(superpos_paulis))) 168 | # Processes the non-Cholesky operator 169 | identity_idx = pauli_ordering_for_tensor_states[identity_string] 170 | identity_idx_superpos = pauli_ordering_for_superpos_states[identity_string] 171 | for pname_i, w_i in paulis_each_op[0].items(): 172 | i = pauli_ordering_for_tensor_states[pname_i] 173 | w_ij[i, identity_idx] += np.real(w_i) # H_spin-up 174 | w_ij[identity_idx, i] += np.real(w_i) # H_spin-down 175 | 176 | # In the special case where bn=bm, we need terms from the 177 | # single body system represented in the cross terms. 178 | if self._calculate_tensor_cross_terms: 179 | w_ab[i, identity_idx_superpos] += np.real(w_i) 180 | w_ab[identity_idx_superpos, i] += np.real(w_i) 181 | # Processes the Cholesky operators (indexed by gamma) 182 | for paulis_this_gamma in paulis_each_op[1:]: 183 | for pname_1, w_1 in paulis_this_gamma.items(): 184 | i = pauli_ordering_for_tensor_states[pname_1] 185 | superpos_ordering1 = pauli_ordering_for_superpos_states[ 186 | pname_1 187 | ] # pylint: disable=invalid-name 188 | for pname_2, w_2 in paulis_this_gamma.items(): 189 | j = pauli_ordering_for_tensor_states[pname_2] 190 | superpos_ordering2 = pauli_ordering_for_superpos_states[ 191 | pname_2 192 | ] # pylint: disable=invalid-name 193 | w_ij[i, j] += np.real(w_1 * w_2) 194 | w_ab[superpos_ordering1, superpos_ordering2] += np.real( 195 | w_1 * w_2 196 | ) # pylint: disable=invalid-name 197 | return tensor_paulis, superpos_paulis, w_ij, w_ab 198 | -------------------------------------------------------------------------------- /entanglement_forging/core/orbitals_to_reduce.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """Orbitals to reduce.""" 14 | 15 | import numpy as np 16 | 17 | 18 | class OrbitalsToReduce: 19 | """A class that describes which orbitals (all, occupied and virtual) to reduce. 20 | Attributes: 21 | all (list): All orbitals to be reduced. 22 | occupied (list): Only the occupied orbitals to be reduced. 23 | virtual (list): Only the virtual orbitals to be reduced. 24 | """ 25 | 26 | def __init__(self, all_orbitals_to_reduce, problem): 27 | """Initialize the orbitals to reduce. 28 | Args: 29 | all_orbitals_to_reduce (list): All orbitals to be reduced. 30 | problem (ElectronicStructureProblem): Problem class which holds driver 31 | """ 32 | self.problem = problem 33 | self.all = all_orbitals_to_reduce 34 | 35 | def occupied(self): 36 | """Returns occupied orbitals.""" 37 | orbitals_to_reduce_array = np.asarray(self.all) 38 | particle_number = self.problem.grouped_property.get_property("ParticleNumber") 39 | num_alpha = particle_number.num_alpha 40 | return orbitals_to_reduce_array[orbitals_to_reduce_array < num_alpha].tolist() 41 | 42 | def virtual(self): 43 | """Returns virtual orbitals.""" 44 | orbitals_to_reduce_array = np.asarray(self.all) 45 | particle_number = self.problem.grouped_property.get_property("ParticleNumber") 46 | num_alpha = particle_number.num_alpha 47 | return orbitals_to_reduce_array[orbitals_to_reduce_array >= num_alpha].tolist() 48 | -------------------------------------------------------------------------------- /entanglement_forging/core/wrappers/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """ 14 | Subclasses extending Qiskit classes 15 | """ 16 | -------------------------------------------------------------------------------- /entanglement_forging/core/wrappers/entanglement_forged_driver.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """EntanglementForgedDriver.""" 14 | 15 | import numpy as np 16 | from qiskit_nature.drivers import Molecule 17 | from qiskit_nature.drivers.second_quantization import ElectronicStructureDriver 18 | from qiskit_nature.properties.second_quantization.electronic import ( 19 | ElectronicStructureDriverResult, 20 | ParticleNumber, 21 | ElectronicEnergy, 22 | ) 23 | from qiskit_nature.properties.second_quantization.electronic.integrals import ( 24 | ElectronicIntegrals, 25 | OneBodyElectronicIntegrals, 26 | TwoBodyElectronicIntegrals, 27 | ) 28 | 29 | from qiskit_nature.properties.second_quantization.electronic.bases import ( 30 | ElectronicBasis, 31 | ElectronicBasisTransform, 32 | ) 33 | 34 | 35 | class EntanglementForgedDriver(ElectronicStructureDriver): 36 | """EntanglementForgedDriver.""" 37 | 38 | # pylint: disable=too-many-arguments 39 | def __init__( 40 | self, 41 | hcore: np.ndarray, 42 | mo_coeff: np.ndarray, 43 | eri: np.ndarray, 44 | num_alpha: int, 45 | num_beta: int, 46 | nuclear_repulsion_energy: float, 47 | ): 48 | """Entanglement forging driver 49 | 50 | Args: 51 | hcore: hcore integral 52 | mo_coeff: MO coefficients 53 | eri: eri integral 54 | num_alpha: number of alpha electrons 55 | num_beta: number of beta electrons 56 | nuclear_repulsion_energy: nuclear repulsion energy 57 | """ 58 | super().__init__() 59 | 60 | self._hcore = hcore 61 | self._mo_coeff = mo_coeff 62 | self._eri = eri 63 | self._num_alpha = num_alpha 64 | self._num_beta = num_beta 65 | self._nuclear_repulsion_energy = nuclear_repulsion_energy 66 | 67 | def run(self) -> ElectronicStructureDriverResult: 68 | """Returns QMolecule constructed from input data.""" 69 | # Create ParticleNumber property. Multiply by 2 since the number 70 | # of spin orbitals is 2x the number of MOs 71 | particle_number = ParticleNumber( 72 | self._mo_coeff.shape[0] * 2, (self._num_alpha, self._num_beta) 73 | ) 74 | 75 | # Define the transform from AO to MO 76 | elx_basis_xform = ElectronicBasisTransform( 77 | ElectronicBasis.AO, ElectronicBasis.MO, self._mo_coeff 78 | ) 79 | 80 | # One and two-body integrals in AO basis 81 | one_body_ao = OneBodyElectronicIntegrals( 82 | ElectronicBasis.AO, (self._hcore, None) 83 | ) 84 | two_body_ao = TwoBodyElectronicIntegrals( 85 | ElectronicBasis.AO, (self._eri, None, None, None) 86 | ) 87 | 88 | # One and two-body integrals in MO basis 89 | one_body_mo = one_body_ao.transform_basis(elx_basis_xform) 90 | two_body_mo = two_body_ao.transform_basis(elx_basis_xform) 91 | 92 | # Instantiate ElectronicEnergy property object 93 | electronic_energy = ElectronicEnergy( 94 | [one_body_ao, two_body_ao, one_body_mo, two_body_mo], 95 | nuclear_repulsion_energy=self._nuclear_repulsion_energy, 96 | ) 97 | 98 | result = ElectronicStructureDriverResult() 99 | result.add_property(electronic_energy) 100 | result.add_property(particle_number) 101 | result.add_property(elx_basis_xform) 102 | 103 | return result 104 | -------------------------------------------------------------------------------- /entanglement_forging/core/wrappers/entanglement_forged_ground_state_eigensolver.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """Ground state computation using a minimum eigensolver with entanglement forging.""" 14 | 15 | import time 16 | import warnings 17 | from typing import List, Iterable, Union, Dict, Optional, Tuple 18 | 19 | import numpy as np 20 | from qiskit import QuantumCircuit 21 | from qiskit.algorithms import MinimumEigensolver 22 | from qiskit.circuit import Instruction 23 | from qiskit.opflow import OperatorBase, PauliSumOp 24 | from qiskit.quantum_info import Statevector 25 | from qiskit.result import Result 26 | from qiskit_nature import ListOrDictType 27 | from qiskit_nature.algorithms.ground_state_solvers import ( 28 | GroundStateSolver, 29 | MinimumEigensolverFactory, 30 | ) 31 | from qiskit_nature.converters.second_quantization import QubitConverter 32 | from qiskit_nature.mappers.second_quantization import JordanWignerMapper 33 | from qiskit_nature.operators.second_quantization import SecondQuantizedOp 34 | from qiskit_nature.problems.second_quantization import BaseProblem 35 | from qiskit_nature.problems.second_quantization.electronic import ( 36 | ElectronicStructureProblem, 37 | ) 38 | 39 | from entanglement_forging.core.classical_energies import ClassicalEnergies 40 | from entanglement_forging.core.forged_operator import ForgedOperator 41 | from entanglement_forging.core.entanglement_forged_config import ( 42 | EntanglementForgedConfig, 43 | ) 44 | from entanglement_forging.core.wrappers.entanglement_forged_vqe import ( 45 | EntanglementForgedVQE, 46 | ) 47 | from entanglement_forging.core.wrappers.entanglement_forged_vqe_result import ( 48 | EntanglementForgedVQEResult, 49 | OptimalParams, 50 | ) 51 | from entanglement_forging.utils.log import Log 52 | 53 | 54 | # pylint: disable=too-many-arguments,protected-access 55 | class EntanglementForgedGroundStateSolver(GroundStateSolver): 56 | """Ground state computation using a minimum solver with entanglement forging. 57 | 58 | Attr: 59 | transformation: Qubit Operator Transformation 60 | ansatz (Two): 61 | orbitals_to_reduce (list of int): Orbital list to be frozen or removed. 62 | """ 63 | 64 | def __init__( 65 | self, 66 | qubit_converter: QubitConverter, 67 | ansatz: QuantumCircuit, 68 | bitstrings_u: Iterable[Iterable[int]], 69 | config: EntanglementForgedConfig, 70 | bitstrings_v: Iterable[Iterable[int]] = None, 71 | orbitals_to_reduce: bool = None, 72 | ): 73 | # Ensure the bitstrings are well formed 74 | if any(len(bitstrings_u[0]) != len(bitstr) for bitstr in bitstrings_u): 75 | raise ValueError("All U bitstrings must be the same length.") 76 | 77 | if bitstrings_v: 78 | if len(bitstrings_u) != len(bitstrings_v): 79 | raise ValueError( 80 | "The same number of bitstrings should be passed for U and V." 81 | ) 82 | 83 | if len(bitstrings_u[0]) != len(bitstrings_v[0]): 84 | raise ValueError("Bitstrings for U and V should be the same length.") 85 | 86 | if any(len(bitstrings_v[0]) != len(bitstr) for bitstr in bitstrings_v): 87 | raise ValueError("All V bitstrings must be the same length.") 88 | 89 | # Initialize the GroundStateSolver 90 | super().__init__(qubit_converter) 91 | 92 | # Set which orbitals to ignore when calculating the Hamiltonian 93 | if orbitals_to_reduce is None: 94 | orbitals_to_reduce = [] 95 | self.orbitals_to_reduce = orbitals_to_reduce 96 | 97 | # Set private class fields 98 | self._ansatz = ansatz 99 | self._bitstrings_u = bitstrings_u 100 | self._config = config # pylint: disable=arguments-differ 101 | 102 | # Prevent unnecessary duplication of circuits if subsystems are identical 103 | if (bitstrings_v is None) or (bitstrings_u == bitstrings_v): 104 | self._bitstrings_v = [] 105 | else: 106 | self._bitstrings_v = bitstrings_v 107 | 108 | # pylint: disable=arguments-differ 109 | def solve( 110 | self, 111 | problem: ElectronicStructureProblem, 112 | ) -> EntanglementForgedVQEResult: 113 | """Compute Ground State properties of chemical problem. 114 | 115 | Args: 116 | problem: a qiskit_nature.problems.second_quantization 117 | .electronic.electronic_structure_problem object. 118 | aux_operators: additional auxiliary operators to evaluate 119 | **kwargs: keyword args to pass to solver 120 | 121 | Raises: 122 | ValueError: If the transformation is not of the type FermionicTransformation. 123 | ValueError: If the qubit mapping is not of the type JORDAN_WIGNER. 124 | 125 | Returns: 126 | An eigenstate result. 127 | """ 128 | 129 | if not isinstance(problem, ElectronicStructureProblem): 130 | raise ValueError( 131 | "This version only supports an ElectronicStructureProblem." 132 | ) 133 | if not isinstance(self.qubit_converter.mapper, JordanWignerMapper): 134 | raise ValueError("This version only supports the JordanWignerMapper.") 135 | 136 | start_time = time.time() 137 | 138 | problem.driver.run() 139 | 140 | # Decompose the Hamiltonian operators into a form appropraite for EF 141 | forged_operator = ForgedOperator( 142 | problem, self.orbitals_to_reduce, self._calculate_tensor_cross_terms() 143 | ) 144 | 145 | # Calculate energies clasically using pySCF 146 | classical_energies = ClassicalEnergies(problem, self.orbitals_to_reduce) 147 | 148 | self._solver = EntanglementForgedVQE( 149 | ansatz=self._ansatz, 150 | bitstrings_u=self._bitstrings_u, 151 | bitstrings_v=self._bitstrings_v, 152 | config=self._config, 153 | forged_operator=forged_operator, 154 | classical_energies=classical_energies, 155 | ) 156 | result = self._solver.compute_minimum_eigenvalue(forged_operator.h_1_op) 157 | 158 | elapsed_time = time.time() - start_time 159 | Log.log(f"VQE for this problem took {elapsed_time} seconds") 160 | res = EntanglementForgedVQEResult( 161 | parameters_history=self._solver._paramsets_each_iteration, 162 | energies_history=self._solver._energy_each_iteration_each_paramset, 163 | schmidts_history=self._solver._schmidt_coeffs_each_iteration_each_paramset, 164 | energy_std_each_parameter_set=self._solver.energy_std_each_parameter_set, 165 | energy_offset=self._solver._add_this_to_energies_displayed, 166 | eval_count=self._solver._eval_count, 167 | ) 168 | res.combine(result) 169 | return res 170 | 171 | def _calculate_tensor_cross_terms(self) -> bool: 172 | """ 173 | Determine whether circuits should be generated to account for 174 | the special superposition terms needed when bn==bm for two 175 | bitstrings within a given subsystem's (U or V) bitstring list. 176 | """ 177 | bsu = self._bitstrings_u 178 | bsv = self._bitstrings_u 179 | 180 | # Search for any duplicate bitstrings within the subsystem lists 181 | for i, bu1 in enumerate(bsu): 182 | for j, bu2 in enumerate(bsu): 183 | if i == j: 184 | continue 185 | if bu1 == bu2: 186 | return True 187 | for i, bv1 in enumerate(bsv): 188 | for j, bv2 in enumerate(bsv): 189 | if i == j: 190 | continue 191 | if bv1 == bv2: 192 | return True 193 | 194 | return False 195 | 196 | def returns_groundstate(self) -> bool: 197 | """Whether this class returns only the ground state energy or also the ground state itself. 198 | 199 | Returns: 200 | True, if this class also returns the ground state in the results object. 201 | False otherwise. 202 | """ 203 | return True 204 | 205 | def evaluate_operators( 206 | self, 207 | state: Union[ 208 | str, 209 | dict, 210 | Result, 211 | list, 212 | np.ndarray, 213 | Statevector, 214 | QuantumCircuit, 215 | Instruction, 216 | OperatorBase, 217 | ], 218 | operators: Union[PauliSumOp, OperatorBase, list, dict], 219 | ) -> Union[float, Iterable[float], Dict[str, Iterable[float]]]: 220 | """Evaluates additional operators at the given state.""" 221 | warnings.warn( 222 | "evaluate_operators not implemented for " 223 | "forged EntanglementForgedGroundStateSolver." 224 | ) 225 | return [] 226 | 227 | @property 228 | def solver(self) -> Union[MinimumEigensolver, MinimumEigensolverFactory]: 229 | """Returns the minimum eigensolver or factory.""" 230 | return self._solver 231 | 232 | def get_qubit_operators( 233 | self, 234 | problem: BaseProblem, 235 | aux_operators: Optional[ 236 | ListOrDictType[Union[SecondQuantizedOp, PauliSumOp]] 237 | ] = None, 238 | ) -> Tuple[PauliSumOp, Optional[ListOrDictType[PauliSumOp]]]: 239 | """Gets the operator and auxiliary operators, and transforms the provided auxiliary operators""" 240 | raise NotImplementedError( 241 | "get_qubit_operators has not been implemented in EntanglementForgedGroundStateEigensolver" 242 | ) 243 | -------------------------------------------------------------------------------- /entanglement_forging/core/wrappers/entanglement_forged_vqe_result.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """VQE results""" 14 | 15 | import inspect 16 | import pprint 17 | from collections import OrderedDict 18 | from typing import List, Optional, Union, Tuple 19 | 20 | import numpy as np 21 | from qiskit.algorithms.minimum_eigen_solvers.vqe import VQEResult 22 | 23 | 24 | # pylint: disable=too-few-public-methods,too-many-arguments 25 | class AuxiliaryResults: 26 | """Base class for auxiliary results.""" 27 | 28 | def __str__(self) -> str: 29 | result = OrderedDict() 30 | for name, value in inspect.getmembers(self): 31 | if ( 32 | not name.startswith("_") 33 | and not inspect.ismethod(value) 34 | and not inspect.isfunction(value) 35 | and hasattr(self, name) 36 | ): 37 | result[name] = value 38 | 39 | return pprint.pformat(result, indent=4) 40 | 41 | def __repr__(self): 42 | key_value_pairs = [ 43 | f"{name}: {value}" 44 | for name, value in inspect.getmembers(self) 45 | if not name.startswith("_") 46 | and not inspect.ismethod(value) 47 | and not inspect.isfunction(value) 48 | and hasattr(self, name) 49 | ] 50 | return f"{self.__class__.__name__}({key_value_pairs})" 51 | 52 | 53 | class OptimalParams(AuxiliaryResults): 54 | """Optional params results.""" 55 | 56 | def __init__(self, energy: float, optimal_params: List[float]): 57 | """Optimal parameters. 58 | 59 | Args: 60 | energy: energy hartree 61 | optimal_params: optimal parameters for VQE 62 | """ 63 | self.energy = energy 64 | self.optimal_params = optimal_params 65 | 66 | 67 | class Bootstrap(AuxiliaryResults): 68 | """Bootstrap results.""" 69 | 70 | def __init__(self, eval_count: int, eval_timestamp, parameters, bootstrap_values): 71 | """Bootstrap parameters. 72 | 73 | Args: 74 | eval_count: 75 | eval_timestamp: 76 | parameters: 77 | bootstrap_values: 78 | """ 79 | self.eval_count = eval_count 80 | self.eval_timestamp = eval_timestamp 81 | self.parameters = parameters 82 | self.boostrap_values = bootstrap_values 83 | 84 | 85 | class DataResults(AuxiliaryResults): 86 | """Data results.""" 87 | 88 | def __init__( 89 | self, 90 | eval_count, 91 | eval_timestamp, 92 | energy_hartree, 93 | energy_std, 94 | parameters, 95 | schmidts, 96 | ): 97 | """Data results. 98 | 99 | Args: 100 | eval_count: 101 | eval_timestamp: 102 | energy_hartree: 103 | energy_std: 104 | parameters: 105 | schmidts: 106 | """ 107 | self.eval_count = eval_count 108 | self.eval_timestamp = eval_timestamp 109 | self.energy_hartree = energy_hartree 110 | self.energy_str = energy_std 111 | self.parameters = parameters 112 | self.schmidts = schmidts 113 | 114 | 115 | class EntanglementForgedVQEResult(VQEResult): 116 | """Entanglement-forged VQE Result.""" 117 | 118 | def __init__( 119 | self, 120 | parameters_history: Optional[List[List[np.ndarray]]] = None, 121 | energies_history: Optional[List[List[np.ndarray]]] = None, 122 | schmidts_history: Optional[List[List[np.ndarray]]] = None, 123 | energy_std_each_parameter_set: Optional[List[Union[float, int]]] = None, 124 | energy_offset: Optional[float] = None, 125 | eval_count: Optional[int] = None, 126 | ) -> None: 127 | """Results for EntanglementForgedGroundStateSolver. 128 | 129 | Args: 130 | parameters_history: 131 | energies_history: 132 | schmidts_history: 133 | energy_std_each_parameter_set: 134 | energy_offset: 135 | eval_count: 136 | auxiliary_results: additional results (on order to remove writing to filesystem) 137 | """ 138 | super().__init__() 139 | 140 | self._parameters_history = parameters_history or [] 141 | self._energies_history = energies_history or [] 142 | self._schmidts_history = schmidts_history or [] 143 | 144 | self._energy_std_each_parameter_set = energy_std_each_parameter_set 145 | self._energy_offset = energy_offset 146 | self._eval_count = eval_count 147 | 148 | def __repr__(self): 149 | return ( 150 | f"Ground state energy (Hartree): {self.ground_state_energy}\n" 151 | f"Schmidt values: {self.schmidts_value}\n" 152 | f"Optimizer parameters: {self.optimizer_parameters}" 153 | ) 154 | 155 | def get_parameters_history(self): 156 | """Returns a list of the optimizer parameters at each iteration.""" 157 | return self._parameters_history 158 | 159 | def get_schmidts_history(self): 160 | """Returns a list of the Schmidt values at each iteration.""" 161 | return self._schmidts_history 162 | 163 | def get_energies_history(self): 164 | """Returns a list of the energy at each iteration.""" 165 | return [[j + self._energy_offset for j in i] for i in self._energies_history] 166 | 167 | @property 168 | def ground_state_energy(self): 169 | """Returns ground state energy.""" 170 | return self.get_energies_history()[-1][0] 171 | 172 | @property 173 | def schmidts_value(self): 174 | """Returns Schmidts value.""" 175 | return self._schmidts_history[-1][0] 176 | 177 | @property 178 | def optimizer_parameters(self): 179 | """Returns optimizer parameters.""" 180 | return self._parameters_history[-1][0] 181 | 182 | @property 183 | def energy_offset(self) -> Optional[float]: 184 | """Returns energy offset.""" 185 | return self._energy_offset 186 | 187 | @property 188 | def energy_std_each_parameter_set(self) -> Optional[List[Union[float, int]]]: 189 | """Returns energy std for each parameters set.""" 190 | return self._energy_std_each_parameter_set 191 | 192 | @property 193 | def eval_count(self) -> Optional[int]: 194 | """Returns evaluation count.""" 195 | return self._eval_count 196 | -------------------------------------------------------------------------------- /entanglement_forging/utils/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """ 14 | Repository of useful utility functions for calibrating, preparing, 15 | running, and analyzing research-grade quantum experiments. 16 | """ 17 | -------------------------------------------------------------------------------- /entanglement_forging/utils/bootstrap_result.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """Bootstrap result.""" 14 | 15 | from collections import Counter 16 | 17 | import numpy as np 18 | 19 | from entanglement_forging.utils.combined_result import CombinedResult 20 | 21 | 22 | def resample_counts(counts_dict): 23 | """Returns resampled counts.""" 24 | labels = list(counts_dict.keys()) 25 | counts = list(counts_dict.values()) 26 | total = int(sum(counts)) 27 | counts = np.array(counts, dtype=float) 28 | counts /= counts.sum() 29 | new_counts_dict = dict( 30 | Counter(np.random.default_rng().choice(labels, total, p=counts)) 31 | ) 32 | return new_counts_dict 33 | 34 | 35 | def resample_result(result): 36 | """Returns resampled results.""" 37 | # TODO Optimize this for speed or move out of experimental routine into post-processing # pylint: disable=fixme 38 | return CombinedResult( 39 | result.results.keys(), 40 | [resample_counts(counts) for counts in result.results.values()], 41 | ) 42 | -------------------------------------------------------------------------------- /entanglement_forging/utils/combined_result.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """Combined result.""" 14 | 15 | 16 | # pylint: disable=too-few-public-methods 17 | class CombinedResult: 18 | """CombinedResult. Giving just enough functionality for Aqua to process it OK""" 19 | 20 | def __init__(self, expt_names, counts_each_expt): 21 | """CombinedResult. 22 | 23 | Args: 24 | expt_names: 25 | counts_each_expt: 26 | """ 27 | self.counts_each_expt = counts_each_expt 28 | self.results = dict(zip(expt_names, counts_each_expt)) 29 | 30 | def get_counts(self, experiment): 31 | """Returns counts.""" 32 | if isinstance(experiment, int): 33 | counts = self.counts_each_expt[experiment] 34 | else: 35 | counts = self.results[experiment] 36 | return counts 37 | -------------------------------------------------------------------------------- /entanglement_forging/utils/copysample_circuits.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """Approximation of a weighted sampling of circuit.""" 14 | 15 | import re 16 | from collections import Counter 17 | 18 | import numpy as np 19 | 20 | from entanglement_forging.utils.combined_result import CombinedResult 21 | 22 | 23 | def copysample_circuits(circuits_to_execute, weights, new_job_size=800): 24 | """Approximates a weighted sampling of circuits. 25 | See "Weighted Sampling of Circuits: Implementation" in .""" 26 | 27 | # WARNING! THIS DOES MUTATE THE INPUT CIRCUITS (MODIFIES THEIR NAMES) 28 | # Could be avoided with a deepcopy, but for now left that out since 29 | # I didn't need it and worried it might be slow. Could add an argument 30 | # to optionally trigger the deepcopy if desired. 31 | 32 | # e.g. if expectation values E_i of all circuits are to be 33 | # added together as sum(w_i * E_i), then to minimize total variance: weights = w_i. 34 | 35 | new_job_size = int(new_job_size) 36 | num_initial_circuits = len(circuits_to_execute) 37 | assert ( 38 | new_job_size >= num_initial_circuits 39 | ), "Try increasing copysample_job_size." # turn this into a proper RaiseError 40 | 41 | weights = np.array(weights) / np.sum(weights) 42 | 43 | # First ensure everything gets measured at least once 44 | # (safer to have too-broad tails of sampling distribution). 45 | # In the future, may want to change this behavior for use-cases 46 | # w very large numbers of circuits, but analysis will need 47 | # to be written to accommodate possibility of some circuits 48 | # being completely absent from results. 49 | copies_each_circuit = np.ones(num_initial_circuits) 50 | copies_still_wanted_each_circuit = weights * new_job_size 51 | copies_still_wanted_each_circuit[copies_still_wanted_each_circuit < 1] = 1 52 | copies_still_wanted_each_circuit -= 1 53 | copies_still_wanted_each_circuit *= (new_job_size - num_initial_circuits) / np.sum( 54 | copies_still_wanted_each_circuit 55 | ) 56 | num_slots_available = new_job_size - num_initial_circuits 57 | 58 | easy_copies = np.floor(copies_still_wanted_each_circuit) 59 | copies_each_circuit += easy_copies 60 | num_slots_available -= np.sum(easy_copies) 61 | copies_still_wanted_each_circuit %= 1 62 | 63 | if np.sum(copies_still_wanted_each_circuit): 64 | prob_for_randomly_picking_from_leftovers = ( 65 | copies_still_wanted_each_circuit / np.sum(copies_still_wanted_each_circuit) 66 | ) 67 | hard_to_fill_idxs = np.random.choice( 68 | num_initial_circuits, 69 | p=prob_for_randomly_picking_from_leftovers, 70 | size=int(round(new_job_size - np.sum(copies_each_circuit))), 71 | replace=False, 72 | ) 73 | copies_each_circuit[hard_to_fill_idxs] += 1 74 | 75 | circuit_name_templates = [qc.name + "_copysample{}" for qc in circuits_to_execute] 76 | 77 | for qcirc, num_copies, name_template in zip( 78 | circuits_to_execute, copies_each_circuit, circuit_name_templates 79 | ): 80 | qcirc.name = name_template.format(0) 81 | if num_copies > 1: 82 | circuits_to_execute += [ 83 | qcirc.copy(name=name_template.format(i)) 84 | for i in range(1, int(num_copies)) 85 | ] 86 | return circuits_to_execute 87 | 88 | 89 | def combine_copysampled_results(aqua_result): 90 | """Combine results of compysampled circuits.""" 91 | result_each_circuit = aqua_result.results 92 | original_circuit_names = [ 93 | re.sub("_copysample[0-9]+", "", r.header.name) for r in result_each_circuit 94 | ] 95 | deduped_counts = {name: Counter({}) for name in set(original_circuit_names)} 96 | for name, res in zip(original_circuit_names, result_each_circuit): 97 | deduped_counts[name] += Counter(aqua_result.get_counts(res.header.name)) 98 | return CombinedResult( 99 | original_circuit_names, 100 | [deduped_counts[name] for name in original_circuit_names], 101 | ) 102 | -------------------------------------------------------------------------------- /entanglement_forging/utils/generic_execution_subroutines.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """Execution subroutines.""" 14 | 15 | import time 16 | 17 | import numpy as np 18 | from qiskit import assemble 19 | from qiskit.providers.ibmq.job import ( 20 | IBMQJobFailureError, 21 | IBMQJobApiError, 22 | IBMQJobInvalidStateError, 23 | ) 24 | 25 | from entanglement_forging.utils.legacy.common import measure_pauli_z, covariance 26 | 27 | 28 | def compute_pauli_means_and_cov_for_one_basis(paulis, counts): 29 | """Compute Pauli means and cov for one basis.""" 30 | means = np.array([measure_pauli_z(counts, pauli) for pauli in paulis]) 31 | cov = np.array( 32 | [ 33 | [ 34 | covariance(counts, pauli_1, pauli_2, avg_1, avg_2) 35 | for pauli_2, avg_2 in zip(paulis, means) 36 | ] 37 | for pauli_1, avg_1 in zip(paulis, means) 38 | ] 39 | ) 40 | return means, cov 41 | 42 | 43 | def execute_with_retry(circuits, backend, shots, rep_delay=None, noise_model=None): 44 | """Executes job with retry.""" 45 | global result # pylint: disable=global-variable-undefined,invalid-name 46 | trials = 0 47 | ran_job_ok = False 48 | while not ran_job_ok: 49 | try: 50 | if backend.name() in [ 51 | "statevector_simulator", 52 | "aer_simulator_statevector", 53 | ]: 54 | job = backend.run( 55 | circuits, 56 | seed_simulator=42, 57 | ) 58 | elif backend.name() == "qasm_simulator": 59 | job = backend.run(circuits, shots=shots, noise_model=noise_model) 60 | else: 61 | job = backend.run(circuits, shots=shots, rep_delay=rep_delay) 62 | 63 | result = job.result() 64 | ran_job_ok = True 65 | except (IBMQJobFailureError, IBMQJobApiError, IBMQJobInvalidStateError) as err: 66 | print("Error running job, will retry in 5 mins.") 67 | print("Error:", err) 68 | # Wait 5 mins and try again. Hopefully this handles network outages etc, 69 | # and also if user cancels a (stuck) job through IQX. 70 | # Add more error types to the exception as new ones crop up (as appropriate). 71 | time.sleep(300) 72 | trials += 1 73 | # pylint: disable=raise-missing-from 74 | if trials > 100: 75 | raise RuntimeError( 76 | "Timed out trying to run job successfully (100 attempts)" 77 | ) 78 | return result 79 | 80 | 81 | def reduce_bitstrings(bitstrings, orbitals_to_reduce): 82 | """Returns reduced bitstrings.""" 83 | return np.delete(bitstrings, orbitals_to_reduce, axis=-1).tolist() 84 | -------------------------------------------------------------------------------- /entanglement_forging/utils/legacy/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """ 14 | Legacy Operators (:mod:`qiskit.aqua.operators.legacy`) 15 | ====================================================== 16 | 17 | .. currentmodule:: qiskit.aqua.operators.legacy 18 | 19 | These are the Operators provided by Aqua up until the 0.6 release. These are being replaced 20 | by the operator flow function and we encourage you to use this. 21 | 22 | Note: 23 | At some future time this legacy operator logic will be deprecated and removed. 24 | 25 | Legacy Operators 26 | ================ 27 | 28 | .. autosummary:: 29 | :toctree: ../stubs/ 30 | :nosignatures: 31 | 32 | TPBGroupedWeightedPauliOperator 33 | 34 | """ 35 | 36 | from .tpb_grouped_weighted_pauli_operator import TPBGroupedWeightedPauliOperator 37 | 38 | __all__ = ["TPBGroupedWeightedPauliOperator"] 39 | -------------------------------------------------------------------------------- /entanglement_forging/utils/legacy/base_operator.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """ Base Operator """ 14 | 15 | from abc import ABC, abstractmethod 16 | 17 | 18 | class LegacyBaseOperator(ABC): 19 | """Operators relevant for quantum applications.""" 20 | 21 | @abstractmethod 22 | def __init__(self, basis=None, z2_symmetries=None, name=None): 23 | """Constructor.""" 24 | self._basis = basis 25 | self._z2_symmetries = z2_symmetries 26 | self._name = name if name is not None else "" 27 | 28 | @property 29 | def name(self): 30 | """returns name""" 31 | return self._name 32 | 33 | @name.setter 34 | def name(self, new_value): 35 | """sets name""" 36 | self._name = new_value 37 | 38 | @property 39 | def basis(self): 40 | """returns basis""" 41 | return self._basis 42 | 43 | @property 44 | def z2_symmetries(self): 45 | """returns z2 symmetries""" 46 | return self._z2_symmetries 47 | 48 | @abstractmethod 49 | def __add__(self, other): 50 | """Overload + operation""" 51 | raise NotImplementedError 52 | 53 | @abstractmethod 54 | def __iadd__(self, other): 55 | """Overload += operation""" 56 | raise NotImplementedError 57 | 58 | @abstractmethod 59 | def __sub__(self, other): 60 | """Overload - operation""" 61 | raise NotImplementedError 62 | 63 | @abstractmethod 64 | def __isub__(self, other): 65 | """Overload -= operation""" 66 | raise NotImplementedError 67 | 68 | @abstractmethod 69 | def __neg__(self): 70 | """Overload unary -""" 71 | raise NotImplementedError 72 | 73 | @abstractmethod 74 | def __eq__(self, other): 75 | """Overload == operation""" 76 | raise NotImplementedError 77 | 78 | @abstractmethod 79 | def __str__(self): 80 | """Overload str()""" 81 | raise NotImplementedError 82 | 83 | @abstractmethod 84 | def __mul__(self, other): 85 | """Overload *""" 86 | raise NotImplementedError 87 | 88 | @abstractmethod 89 | def to_opflow(self): 90 | """Convert to new Operator format.""" 91 | raise NotImplementedError 92 | 93 | @abstractmethod 94 | def construct_evaluation_circuit(self, wave_function, statevector_mode, **kwargs): 95 | """Build circuits to compute the expectation w.r.t the wavefunction.""" 96 | raise NotImplementedError 97 | 98 | @abstractmethod 99 | def evaluate_with_result(self, result, statevector_mode, **kwargs): 100 | """ 101 | Consume the result from the quantum computer to build the expectation, 102 | will be only used along with the :meth:`construct_evaluation_circuit` method. 103 | """ 104 | raise NotImplementedError 105 | 106 | # pylint: disable=too-many-arguments 107 | @abstractmethod 108 | def evolve( 109 | self, 110 | state_in, 111 | evo_time, 112 | num_time_slices, 113 | expansion_mode, 114 | expansion_order, 115 | **kwargs 116 | ): 117 | r""" 118 | Time evolution, exp\^(-jt H). 119 | """ 120 | raise NotImplementedError 121 | 122 | @abstractmethod 123 | def print_details(self): 124 | """print details""" 125 | raise NotImplementedError 126 | 127 | @abstractmethod 128 | def chop(self, threshold, copy=False): 129 | """chop""" 130 | raise NotImplementedError 131 | 132 | @property 133 | @abstractmethod 134 | def num_qubits(self): 135 | """Returns number of qubits for operator""" 136 | raise NotImplementedError 137 | 138 | @abstractmethod 139 | def is_empty(self): 140 | """Check Operator is empty or not""" 141 | raise NotImplementedError 142 | -------------------------------------------------------------------------------- /entanglement_forging/utils/legacy/common.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """ pauli common functions """ 14 | 15 | import logging 16 | 17 | import numpy as np 18 | from qiskit import QuantumCircuit, QuantumRegister 19 | from qiskit.algorithms import AlgorithmError 20 | from qiskit.circuit import Parameter, ParameterExpression 21 | from qiskit.qasm import pi 22 | from qiskit.quantum_info import Pauli # pylint: disable=unused-import 23 | 24 | logger = logging.getLogger(__name__) 25 | 26 | 27 | # pylint: disable=too-many-arguments,too-many-branches,too-many-locals 28 | def pauli_measurement(circuit, pauli, qreg, creg, barrier=False): 29 | """ 30 | Add the proper post-rotation gate on the circuit. 31 | 32 | Args: 33 | circuit (QuantumCircuit): the circuit to be modified. 34 | pauli (Pauli): the pauli will be added. 35 | qreg (QuantumRegister): the quantum register associated with the circuit. 36 | creg (ClassicalRegister): the classical register associated with the circuit. 37 | barrier (bool, optional): whether or not add barrier before measurement. 38 | 39 | Returns: 40 | QuantumCircuit: the original circuit object with post-rotation gate 41 | """ 42 | num_qubits = pauli.num_qubits 43 | for qubit_idx in range(num_qubits): 44 | if pauli.x[qubit_idx]: 45 | if pauli.z[qubit_idx]: 46 | # Measure Y 47 | circuit.sdg(qreg[qubit_idx]) # sdg 48 | circuit.h(qreg[qubit_idx]) # h 49 | else: 50 | # Measure X 51 | circuit.h(qreg[qubit_idx]) # h 52 | if barrier: 53 | circuit.barrier(qreg[qubit_idx]) 54 | circuit.measure(qreg[qubit_idx], creg[qubit_idx]) 55 | 56 | return circuit 57 | 58 | 59 | def measure_pauli_z(data, pauli): 60 | """ 61 | Appropriate post-rotations on the state are assumed. 62 | 63 | Args: 64 | data (dict): a dictionary of the form data = {'00000': 10} ({str: int}) 65 | pauli (Pauli): a Pauli object 66 | 67 | Returns: 68 | float: Expected value of paulis given data 69 | """ 70 | observable = 0.0 71 | num_shots = sum(data.values()) 72 | p_z_or_x = np.logical_or(pauli.z, pauli.x) 73 | for key, value in data.items(): 74 | bitstr = np.asarray(list(key))[::-1].astype(int).astype(bool) 75 | # pylint: disable=no-member 76 | sign = -1.0 if np.logical_xor.reduce(np.logical_and(bitstr, p_z_or_x)) else 1.0 77 | observable += sign * value 78 | observable /= num_shots 79 | return observable 80 | 81 | 82 | def covariance(data, pauli_1, pauli_2, avg_1, avg_2): 83 | """ 84 | Compute the covariance matrix element between two 85 | Paulis, given the measurement outcome. 86 | Appropriate post-rotations on the state are assumed. 87 | 88 | Args: 89 | data (dict): a dictionary of the form data = {'00000': 10} ({str:int}) 90 | pauli_1 (Pauli): a Pauli class member 91 | pauli_2 (Pauli): a Pauli class member 92 | avg_1 (float): expectation value of pauli_1 on `data` 93 | avg_2 (float): expectation value of pauli_2 on `data` 94 | 95 | Returns: 96 | float: the element of the covariance matrix between two Paulis 97 | """ 98 | cov = 0.0 99 | num_shots = sum(data.values()) 100 | 101 | if num_shots == 1: 102 | return cov 103 | 104 | p1_z_or_x = np.logical_or(pauli_1.z, pauli_1.x) 105 | p2_z_or_x = np.logical_or(pauli_2.z, pauli_2.x) 106 | for key, value in data.items(): 107 | bitstr = np.asarray(list(key))[::-1].astype(int).astype(bool) 108 | # pylint: disable=no-member 109 | sign_1 = ( 110 | -1.0 if np.logical_xor.reduce(np.logical_and(bitstr, p1_z_or_x)) else 1.0 111 | ) 112 | sign_2 = ( 113 | -1.0 if np.logical_xor.reduce(np.logical_and(bitstr, p2_z_or_x)) else 1.0 114 | ) 115 | cov += (sign_1 - avg_1) * (sign_2 - avg_2) * value 116 | cov /= num_shots - 1 117 | return cov 118 | 119 | 120 | # pylint: disable=invalid-name 121 | def suzuki_expansion_slice_pauli_list(pauli_list, lam_coef, expansion_order): 122 | """ 123 | Compute the list of pauli terms for a single slice of the suzuki expansion following the paper 124 | https://arxiv.org/pdf/quant-ph/0508139.pdf. 125 | 126 | Args: 127 | pauli_list (list[list[complex, Pauli]]): The slice's weighted Pauli list for the 128 | suzuki expansion 129 | lam_coef (float): The parameter lambda as defined in said paper, 130 | adjusted for the evolution time and the number of time slices 131 | expansion_order (int): The order for suzuki expansion 132 | 133 | Returns: 134 | list: slice pauli list 135 | """ 136 | if expansion_order == 1: 137 | half = [[lam_coef / 2 * c, p] for c, p in pauli_list] 138 | res = half + list(reversed(half)) 139 | else: 140 | p_k = (4 - 4 ** (1 / (2 * expansion_order - 1))) ** -1 141 | side_base = suzuki_expansion_slice_pauli_list( 142 | pauli_list, lam_coef * p_k, expansion_order - 1 143 | ) 144 | side = side_base * 2 145 | middle = suzuki_expansion_slice_pauli_list( 146 | pauli_list, lam_coef * (1 - 4 * p_k), expansion_order - 1 147 | ) 148 | res = side + middle + side 149 | return res 150 | 151 | 152 | def check_commutativity(op_1, op_2, anti=False): 153 | """ 154 | Check the (anti-)commutativity between two operators. 155 | 156 | Args: 157 | op_1 (WeightedPauliOperator): operator 158 | op_2 (WeightedPauliOperator): operator 159 | anti (bool): if True, check anti-commutativity, otherwise check commutativity. 160 | 161 | Returns: 162 | bool: whether or not two operators are commuted or anti-commuted. 163 | """ 164 | com = op_1 * op_2 - op_2 * op_1 if not anti else op_1 * op_2 + op_2 * op_1 165 | com.simplify() 166 | return bool(com.is_empty()) 167 | 168 | 169 | # pylint: disable=too-many-statements 170 | def evolution_instruction( 171 | pauli_list, 172 | evo_time, 173 | num_time_slices, 174 | controlled=False, 175 | power=1, 176 | use_basis_gates=True, 177 | shallow_slicing=False, 178 | barrier=False, 179 | ): 180 | """ 181 | Construct the evolution circuit according to the supplied specification. 182 | 183 | Args: 184 | pauli_list (list([[complex, Pauli]])): The list of pauli terms corresponding 185 | to a single time slice to be evolved 186 | evo_time (Union(complex, float, Parameter, ParameterExpression)): The evolution time 187 | num_time_slices (int): The number of time slices for the expansion 188 | controlled (bool, optional): Controlled circuit or not 189 | power (int, optional): The power to which the unitary operator is to be raised 190 | use_basis_gates (bool, optional): boolean flag for indicating only using basis 191 | gates when building circuit. 192 | shallow_slicing (bool, optional): boolean flag for indicating using shallow 193 | qc.data reference repetition for slicing 194 | barrier (bool, optional): whether or not add barrier for every slice 195 | 196 | Returns: 197 | Instruction: The Instruction corresponding to specified evolution. 198 | 199 | Raises: 200 | AlgorithmError: power must be an integer and greater or equal to 1 201 | ValueError: Unrecognized pauli 202 | """ 203 | 204 | if not isinstance(power, int) or power < 1: 205 | raise AlgorithmError("power must be an integer and greater or equal to 1.") 206 | 207 | state_registers = QuantumRegister(pauli_list[0][1].num_qubits) 208 | if controlled: 209 | inst_name = f"Controlled-Evolution^{power}" 210 | ancillary_registers = QuantumRegister(1) 211 | qc_slice = QuantumCircuit(state_registers, ancillary_registers, name=inst_name) 212 | else: 213 | inst_name = f"Evolution^{power}" 214 | qc_slice = QuantumCircuit(state_registers, name=inst_name) 215 | 216 | # for each pauli [IXYZ]+, record the list of qubit pairs needing CX's 217 | cnot_qubit_pairs = [None] * len(pauli_list) 218 | # for each pauli [IXYZ]+, record the highest index of the nontrivial pauli gate (X,Y, or Z) 219 | top_xyz_pauli_indices = [-1] * len(pauli_list) 220 | 221 | for pauli_idx, pauli in enumerate(reversed(pauli_list)): 222 | n_qubits = pauli[1].num_qubits 223 | # changes bases if necessary 224 | nontrivial_pauli_indices = [] 225 | for qubit_idx in range(n_qubits): 226 | # pauli I 227 | if not pauli[1].z[qubit_idx] and not pauli[1].x[qubit_idx]: 228 | continue 229 | 230 | if cnot_qubit_pairs[pauli_idx] is None: 231 | nontrivial_pauli_indices.append(qubit_idx) 232 | 233 | if pauli[1].x[qubit_idx]: 234 | # pauli X 235 | if not pauli[1].z[qubit_idx]: 236 | if use_basis_gates: 237 | qc_slice.h(state_registers[qubit_idx]) 238 | else: 239 | qc_slice.h(state_registers[qubit_idx]) 240 | # pauli Y 241 | elif pauli[1].z[qubit_idx]: 242 | if use_basis_gates: 243 | qc_slice.u(pi / 2, -pi / 2, pi / 2, state_registers[qubit_idx]) 244 | else: 245 | qc_slice.rx(pi / 2, state_registers[qubit_idx]) 246 | # pauli Z 247 | elif pauli[1].z[qubit_idx] and not pauli[1].x[qubit_idx]: 248 | pass 249 | else: 250 | raise ValueError(f"Unrecognized pauli: {pauli[1]}") 251 | 252 | if nontrivial_pauli_indices: 253 | top_xyz_pauli_indices[pauli_idx] = nontrivial_pauli_indices[-1] 254 | 255 | # insert lhs cnot gates 256 | if cnot_qubit_pairs[pauli_idx] is None: 257 | cnot_qubit_pairs[pauli_idx] = list( 258 | zip( 259 | sorted(nontrivial_pauli_indices)[:-1], 260 | sorted(nontrivial_pauli_indices)[1:], 261 | ) 262 | ) 263 | 264 | for pair in cnot_qubit_pairs[pauli_idx]: 265 | qc_slice.cx(state_registers[pair[0]], state_registers[pair[1]]) 266 | 267 | # insert Rz gate 268 | if top_xyz_pauli_indices[pauli_idx] >= 0: 269 | 270 | # Because Parameter does not support complexity number operation; thus, we do 271 | # the following tricks to generate parameterized instruction. 272 | # We assume the coefficient in the pauli is always real. and can not do imaginary time 273 | # evolution 274 | if isinstance(evo_time, (Parameter, ParameterExpression)): 275 | lam = 2.0 * pauli[0] / num_time_slices 276 | lam = lam.real if lam.imag == 0 else lam 277 | lam = lam * evo_time 278 | else: 279 | lam = (2.0 * pauli[0] * evo_time / num_time_slices).real 280 | 281 | if not controlled: 282 | if use_basis_gates: 283 | qc_slice.p(lam, state_registers[top_xyz_pauli_indices[pauli_idx]]) 284 | else: 285 | qc_slice.rz(lam, state_registers[top_xyz_pauli_indices[pauli_idx]]) 286 | else: 287 | if use_basis_gates: 288 | qc_slice.p( 289 | lam / 2, state_registers[top_xyz_pauli_indices[pauli_idx]] 290 | ) 291 | qc_slice.cx( 292 | ancillary_registers[0], 293 | state_registers[top_xyz_pauli_indices[pauli_idx]], 294 | ) 295 | qc_slice.p( 296 | -lam / 2, state_registers[top_xyz_pauli_indices[pauli_idx]] 297 | ) 298 | qc_slice.cx( 299 | ancillary_registers[0], 300 | state_registers[top_xyz_pauli_indices[pauli_idx]], 301 | ) 302 | else: 303 | qc_slice.crz( 304 | lam, 305 | ancillary_registers[0], 306 | state_registers[top_xyz_pauli_indices[pauli_idx]], 307 | ) 308 | 309 | # insert rhs cnot gates 310 | for pair in reversed(cnot_qubit_pairs[pauli_idx]): 311 | qc_slice.cx(state_registers[pair[0]], state_registers[pair[1]]) 312 | 313 | # revert bases if necessary 314 | for qubit_idx in range(n_qubits): 315 | if pauli[1].x[qubit_idx]: 316 | # pauli X 317 | if not pauli[1].z[qubit_idx]: 318 | if use_basis_gates: 319 | qc_slice.h(state_registers[qubit_idx]) 320 | else: 321 | qc_slice.h(state_registers[qubit_idx]) 322 | # pauli Y 323 | elif pauli[1].z[qubit_idx]: 324 | if use_basis_gates: 325 | qc_slice.u(-pi / 2, -pi / 2, pi / 2, state_registers[qubit_idx]) 326 | else: 327 | qc_slice.rx(-pi / 2, state_registers[qubit_idx]) 328 | # repeat the slice 329 | if shallow_slicing: 330 | logger.info( 331 | "Under shallow slicing mode, the qc.data reference is repeated shallowly. " 332 | "Thus, changing gates of one slice of the output circuit might affect " 333 | "other slices." 334 | ) 335 | if barrier: 336 | qc_slice.barrier(state_registers) 337 | qc_slice.data *= num_time_slices * power 338 | qc = qc_slice 339 | else: 340 | qc = QuantumCircuit(*qc_slice.qregs, name=inst_name) 341 | for _ in range(num_time_slices * power): 342 | qc.append(qc_slice, qc.qubits) 343 | if barrier: 344 | qc.barrier(state_registers) 345 | return qc.to_instruction() 346 | -------------------------------------------------------------------------------- /entanglement_forging/utils/legacy/op_converter.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """ op converter """ 14 | 15 | # pylint: disable=cyclic-import 16 | 17 | import logging 18 | from typing import Union, Callable, cast 19 | 20 | from qiskit.algorithms import AlgorithmError 21 | 22 | from .tpb_grouped_weighted_pauli_operator import TPBGroupedWeightedPauliOperator 23 | from .weighted_pauli_operator import WeightedPauliOperator 24 | 25 | logger = logging.getLogger(__name__) 26 | 27 | 28 | # pylint: disable=invalid-name,no-else-return,no-member 29 | def to_tpb_grouped_weighted_pauli_operator( 30 | operator: Union[WeightedPauliOperator, TPBGroupedWeightedPauliOperator], 31 | grouping_func: Callable, 32 | **kwargs: int, 33 | ) -> TPBGroupedWeightedPauliOperator: 34 | """ 35 | 36 | Args: 37 | operator: one of supported operator type 38 | grouping_func: a callable function that grouped the paulis in the operator. 39 | kwargs: other setting for `grouping_func` function 40 | 41 | Returns: 42 | the converted tensor-product-basis grouped weighted pauli operator 43 | 44 | Raises: 45 | AlgorithmError: Unsupported type to convert 46 | """ 47 | if operator.__class__ == WeightedPauliOperator: 48 | return grouping_func(operator, **kwargs) 49 | elif operator.__class__ == TPBGroupedWeightedPauliOperator: 50 | # different tpb grouping approach is asked 51 | op_tpb = cast(TPBGroupedWeightedPauliOperator, operator) 52 | if grouping_func != op_tpb.grouping_func and kwargs != op_tpb.kwargs: 53 | return grouping_func(op_tpb, **kwargs) 54 | else: 55 | return op_tpb 56 | else: 57 | raise AlgorithmError( 58 | "Unsupported type to convert to TPBGroupedWeightedPauliOperator: " 59 | f"{operator.__class__}" 60 | ) 61 | -------------------------------------------------------------------------------- /entanglement_forging/utils/legacy/pauli_graph.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """ 14 | For coloring Pauli Graph for transforming paulis into grouped Paulis 15 | """ 16 | 17 | import copy 18 | 19 | import numpy as np 20 | 21 | 22 | def _create_nodes(paulis): 23 | """ 24 | Check the validity of the pauli list and return immutable list as list of nodes. 25 | 26 | Args: 27 | paulis (list): list of [weight, Pauli object] 28 | 29 | Returns: 30 | Pauli: object as immutable list 31 | """ 32 | pauli_operators = [x[1] for x in paulis] 33 | pauli_weights = [x[0] for x in paulis] 34 | return tuple(pauli_operators), tuple(pauli_weights) # fix their ordering 35 | 36 | 37 | class PauliGraph: # pylint: disable=too-few-public-methods 38 | """Pauli Graph.""" 39 | 40 | def __init__(self, paulis, mode="largest-degree"): 41 | self.nodes, self.weights = _create_nodes(paulis) # must be pauli list 42 | self._nqbits = self._get_nqbits() 43 | self.edges = self._create_edges() 44 | self._grouped_paulis = self._coloring(mode) 45 | 46 | def _get_nqbits(self): 47 | nqbits = self.nodes[0].num_qubits 48 | for i in range(1, len(self.nodes)): 49 | assert nqbits == self.nodes[i].num_qubits, "different number of qubits" 50 | return nqbits 51 | 52 | def _create_edges(self): 53 | """ 54 | Create edges (i,j) if i and j is not commutable under Paulis. 55 | 56 | Returns: 57 | dict: dictionary of graph connectivity with node index as key and 58 | list of neighbor as values 59 | """ 60 | # pylint: disable=invalid-name 61 | conv = {"I": 0, "X": 1, "Y": 2, "Z": 3} 62 | a = np.array( 63 | [[conv[e] for e in reversed(n.to_label())] for n in self.nodes], 64 | dtype=np.int8, 65 | ) 66 | b = a[:, None] 67 | # i and j are commutable with TPB if c[i, j] is True 68 | c = (((a * b) * (a - b)) == 0).all(axis=2) 69 | # pylint: disable=singleton-comparison 70 | edges = {i: np.where(c[i] == False)[0] for i in range(len(self.nodes))} # noqa 71 | return edges 72 | 73 | def _coloring(self, mode="largest-degree"): # pylint: disable=too-many-locals 74 | # pylint: disable=invalid-name 75 | if mode == "largest-degree": # pylint: disable=no-else-return 76 | nodes = sorted( 77 | self.edges.keys(), key=lambda x: len(self.edges[x]), reverse=True 78 | ) 79 | # -1 means not colored; 0 ... len(self.nodes)-1 is valid colored 80 | max_node = max(nodes) 81 | color = np.array([-1] * (max_node + 1)) 82 | all_colors = np.arange(len(nodes)) 83 | for i in nodes: 84 | neighbors = self.edges[i] 85 | color_neighbors = color[neighbors] 86 | color_neighbors = color_neighbors[color_neighbors >= 0] 87 | mask = np.ones(len(nodes), dtype=bool) 88 | mask[color_neighbors] = False 89 | color[i] = np.min(all_colors[mask]) 90 | assert np.min(color[nodes]) >= 0, "Uncolored node exists!" 91 | 92 | # post-processing to grouped_paulis 93 | max_color = np.max( 94 | color[nodes] 95 | ) # the color used is 0, 1, 2, ..., max_color 96 | temp_gp = [] # list of indices of grouped paulis 97 | for c in range(max_color + 1): # max_color is included 98 | temp_gp.append([i for i, icolor in enumerate(color) if icolor == c]) 99 | 100 | # create _grouped_paulis as dictated in the operator.py 101 | gp = [] 102 | for c in range(max_color + 1): # max_color is included 103 | # add all paulis 104 | gp.append([[self.weights[i], self.nodes[i]] for i in temp_gp[c]]) 105 | 106 | # create header (measurement basis) 107 | for i, groupi in enumerate(gp): 108 | header = [0.0, copy.deepcopy(groupi[0][1])] 109 | for _, p in groupi: 110 | for k in range(self._nqbits): 111 | if p.z[k] or p.x[k]: 112 | header[1].z[k] = p.z[k] 113 | header[1].x[k] = p.x[k] 114 | gp[i].insert(0, header) 115 | return gp 116 | else: 117 | return self._coloring( 118 | "largest-degree" 119 | ) # this is the default implementation 120 | 121 | @property 122 | def grouped_paulis(self): 123 | """Getter of grouped Pauli list.""" 124 | return self._grouped_paulis 125 | -------------------------------------------------------------------------------- /entanglement_forging/utils/legacy/tpb_grouped_weighted_pauli_operator.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """ TPB Grouped Weighted Pauli Operator """ 14 | 15 | import copy 16 | 17 | from .pauli_graph import PauliGraph 18 | from .weighted_pauli_operator import WeightedPauliOperator 19 | 20 | 21 | def _post_format_conversion(grouped_paulis): 22 | # TODO: edit the codes without applying post formatting. # pylint: disable=fixme 23 | basis = [] 24 | paulis = [] 25 | 26 | total_idx = 0 27 | for _, tpb in enumerate(grouped_paulis): 28 | curr_basis = tpb[0][1] 29 | curr_paulis = tpb[1:] 30 | basis.append((curr_basis, list(range(total_idx, total_idx + len(curr_paulis))))) 31 | paulis.extend(curr_paulis) 32 | total_idx += len(curr_paulis) 33 | 34 | return basis, paulis 35 | 36 | 37 | class TPBGroupedWeightedPauliOperator(WeightedPauliOperator): 38 | """ 39 | TPB Grouped Weighted Pauli Operator 40 | """ 41 | 42 | # pylint: disable=too-many-arguments 43 | def __init__( 44 | self, 45 | paulis, 46 | basis, 47 | z2_symmetries=None, 48 | atol=1e-12, 49 | name=None, 50 | grouping_func=None, 51 | kwargs=None, 52 | ): 53 | """ 54 | Args: 55 | paulis (list[[complex, Pauli]]): the list of weighted Paulis, where a weighted pauli is 56 | composed of a length-2 list and the first item is the 57 | weight and the second item is the Pauli object. 58 | basis (list[tuple(object, [int])], optional): the grouping basis, each element is a 59 | tuple composed of the basis 60 | and the indices to paulis which are 61 | belonged to that group. 62 | e.g., if tpb basis is used, the object 63 | will be a pauli. 64 | By default, the group is equal to 65 | non-grouping, each pauli is its own basis. 66 | z2_symmetries (Z2Symmetries): recording the z2 symmetries info 67 | atol (float, optional): the threshold used in truncating paulis 68 | name (str, optional): the name of operator. 69 | grouping_func (Callable, optional): Function to group paulis 70 | kwargs (dict): Optional parameters for grouping function call 71 | """ 72 | super().__init__(paulis, basis, z2_symmetries, atol, name) 73 | self._grouping_func = grouping_func 74 | self._kwargs = kwargs or {} 75 | 76 | @property 77 | def num_groups(self): 78 | """returns number of groups""" 79 | return len(self._basis) 80 | 81 | @property 82 | def grouping_func(self): 83 | """returns grouping function""" 84 | return self._grouping_func 85 | 86 | @property 87 | def kwargs(self): 88 | """returns kwargs""" 89 | return self._kwargs 90 | 91 | @classmethod 92 | def sorted_grouping(cls, weighted_pauli_operator, method="largest-degree"): 93 | """ 94 | Largest-Degree First Coloring for grouping paulis. 95 | 96 | Args: 97 | weighted_pauli_operator (WeightedPauliOperator): the to-be-grouped 98 | weighted pauli operator. 99 | method (str): only `largest-degree` is available now. 100 | 101 | Returns: 102 | TPBGroupedWeightedPauliOperator: operator 103 | """ 104 | p_g = PauliGraph(weighted_pauli_operator.paulis, method) 105 | basis, paulis = _post_format_conversion(p_g.grouped_paulis) 106 | kwargs = {"method": method} 107 | return cls( 108 | paulis, 109 | basis, 110 | weighted_pauli_operator.z2_symmetries, 111 | weighted_pauli_operator.atol, 112 | weighted_pauli_operator.name, 113 | cls.sorted_grouping, 114 | kwargs, 115 | ) 116 | 117 | @classmethod 118 | def unsorted_grouping( 119 | cls, weighted_pauli_operator 120 | ): # pylint: disable=too-many-locals 121 | """ 122 | Greedy and unsorted grouping paulis. 123 | 124 | Args: 125 | weighted_pauli_operator (WeightedPauliOperator): the to-be-grouped 126 | weighted pauli operator. 127 | 128 | Returns: 129 | TPBGroupedWeightedPauliOperator: operator 130 | """ 131 | paulis = weighted_pauli_operator.paulis 132 | temp_paulis = copy.deepcopy(paulis) 133 | n_qubits = paulis[0][1].num_qubits 134 | grouped_paulis = [] 135 | sorted_paulis = [] 136 | 137 | def check_pauli_in_list(target, pauli_list): 138 | ret = False 139 | for pauli in pauli_list: 140 | if target[1] == pauli[1]: 141 | ret = True 142 | break 143 | return ret 144 | 145 | # pylint: disable=too-many-nested-blocks 146 | for i, _ in enumerate(temp_paulis): 147 | p_1 = temp_paulis[i] 148 | if not check_pauli_in_list(p_1, sorted_paulis): 149 | paulis_temp = [] 150 | # pauli_list_temp.extend(p_1) # this is going to signal the total 151 | # post-rotations of the set (set master) 152 | paulis_temp.append(p_1) 153 | paulis_temp.append(copy.deepcopy(p_1)) 154 | paulis_temp[0][0] = 0.0 # zero coeff for HEADER 155 | 156 | for j in range(i + 1, len(temp_paulis)): 157 | p_2 = temp_paulis[j] 158 | if not check_pauli_in_list(p_2, sorted_paulis) and p_1[1] != p_2[1]: 159 | j = 0 160 | for __i in range(n_qubits): 161 | # p_2 is identity, p_1 is identity, p_1 and p_2 has same basis 162 | if not ( 163 | (not p_2[1].z[__i] and not p_2[1].x[__i]) 164 | or (not p_1[1].z[__i] and not p_1[1].x[__i]) 165 | or ( 166 | p_2[1].z[__i] == p_1[1].z[__i] 167 | and p_2[1].x[__i] == p_1[1].x[__i] 168 | ) 169 | ): 170 | break 171 | 172 | # update master, if p_2 is not identity 173 | if p_2[1].z[__i] or p_2[1].x[__i]: 174 | paulis_temp[0][1].z[__i] = p_2[1].z[__i] 175 | paulis_temp[0][1].x[__i] = p_2[1].x[__i] 176 | j += 1 177 | if j == n_qubits: 178 | paulis_temp.append(p_2) 179 | sorted_paulis.append(p_2) 180 | grouped_paulis.append(paulis_temp) 181 | # pylint: enable=too-many-nested-blocks 182 | basis, new_paulis = _post_format_conversion(grouped_paulis) 183 | 184 | return cls( 185 | new_paulis, 186 | basis, 187 | weighted_pauli_operator.z2_symmetries, 188 | weighted_pauli_operator.atol, 189 | weighted_pauli_operator.name, 190 | cls.unsorted_grouping, 191 | ) 192 | 193 | def __eq__(self, other): 194 | """Overload == operation""" 195 | if not super().__eq__(other): 196 | return False 197 | # check basis 198 | if len(self._basis) != len(other.basis): 199 | return False 200 | for basis, indices in self._basis: 201 | found_basis = False 202 | found_indices = [] 203 | for other_basis, other_indices in other.basis: 204 | if basis == other_basis: 205 | found_basis = True 206 | found_indices = other_indices 207 | break 208 | if not found_basis or len(indices) != len(found_indices): 209 | return False 210 | return True 211 | 212 | def __str__(self): 213 | """Overload str()""" 214 | curr_repr = "tpb grouped paulis" 215 | length = len(self._paulis) 216 | name = "" if self._name is None else f"{self._name}: " 217 | ret = ( 218 | f"{name}Representation: {curr_repr}, qubits: " 219 | f"{self.num_qubits}, size: {length}, group: " 220 | f"{len(self._basis)}" 221 | ) 222 | 223 | return ret 224 | 225 | # pylint: disable=duplicate-code 226 | def print_details(self): 227 | """ 228 | Print out the operator in details. 229 | 230 | Returns: 231 | str: a formatted string describes the operator. 232 | """ 233 | if self.is_empty(): 234 | return "Operator is empty." 235 | ret = "" 236 | for basis, indices in self._basis: 237 | ret = "".join([ret, f"TPB: {basis.to_label()} ({len(indices)})\n"]) 238 | for idx in indices: 239 | weight, pauli = self._paulis[idx] 240 | ret = "".join([ret, f"{pauli.to_label()}\t{weight}\n"]) 241 | 242 | return ret 243 | 244 | # pylint: enable=duplicate-code 245 | 246 | # pylint: disable=redefined-outer-name 247 | def _add_or_sub(self, other, operation, copy=True): 248 | """ 249 | Add two operators either extend (in-place) or combine (copy) them. 250 | The addition performs optimized combination of two operators. 251 | If `other` has identical basis, the coefficient are combined rather than 252 | appended. 253 | 254 | Args: 255 | other (TPBGroupedWeightedPauliOperator): to-be-combined operator 256 | operation (callable or str): add or sub callable from operator 257 | copy (bool): working on a copy or self 258 | 259 | Returns: 260 | TPBGroupedWeightedPauliOperator: operator 261 | """ 262 | # perform add or sub in paulis and then re-group it again 263 | ret_op = super()._add_or_sub(other, operation, copy) 264 | ret_op = self._grouping_func(ret_op, **self._kwargs) 265 | return ret_op 266 | 267 | def multiply(self, other): 268 | """ 269 | Perform self * other. 270 | 271 | Note: Grouping of the new operator, will be as per self's grouping. 272 | 273 | Args: 274 | other (TPBGroupedWeightedPauliOperator): an operator 275 | 276 | Returns: 277 | TPBGroupedWeightedPauliOperator: the multiplied operator 278 | """ 279 | ret_op = super().multiply(other) 280 | ret_op = self._grouping_func(ret_op, **self._kwargs) 281 | return ret_op 282 | -------------------------------------------------------------------------------- /entanglement_forging/utils/log.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """Logger.""" 14 | 15 | 16 | class Log: # pylint: disable=too-few-public-methods 17 | """Logger.""" 18 | 19 | VERBOSE = False 20 | 21 | @staticmethod 22 | def log(*args): 23 | """Log arguments.""" 24 | if Log.VERBOSE: 25 | print(*args) 26 | -------------------------------------------------------------------------------- /entanglement_forging/utils/meas_mit_filters_faster.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This code is part of Qiskit. 4 | # 5 | # (C) Copyright IBM 2021. 6 | # 7 | # This code is licensed under the Apache License, Version 2.0. You may 8 | # obtain a copy of this license in the LICENSE.txt file in the root directory 9 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 10 | # 11 | # Any modifications or derivative works of this code must retain this 12 | # copyright notice, and modified files need to carry a notice indicating 13 | # that they have been altered from the originals. 14 | 15 | # pylint: disable=cell-var-from-loop,invalid-name 16 | 17 | 18 | """ 19 | Measurement correction filters. 20 | 21 | """ 22 | from copy import deepcopy 23 | 24 | import numpy as np 25 | import qiskit 26 | import scipy.linalg as la 27 | from qiskit import QiskitError 28 | from qiskit.utils.mitigation.circuits import count_keys 29 | from qiskit.tools import parallel_map 30 | from scipy.optimize import minimize 31 | 32 | 33 | # pylint: disable=too-many-locals,too-many-branches,too-many-nested-blocks,too-many-statements 34 | class MeasurementFilter: 35 | """ 36 | Measurement error mitigation filter. 37 | 38 | Produced from a measurement calibration fitter and can be applied 39 | to data. 40 | 41 | """ 42 | 43 | def __init__(self, cal_matrix: np.matrix, state_labels: list): 44 | """ 45 | Initialize a measurement error mitigation filter using the cal_matrix 46 | from a measurement calibration fitter. 47 | 48 | Args: 49 | cal_matrix: the calibration matrix for applying the correction 50 | state_labels: the states for the ordering of the cal matrix 51 | """ 52 | 53 | self._cal_matrix = cal_matrix 54 | self._state_labels = state_labels 55 | 56 | @property 57 | def cal_matrix(self): 58 | """Return cal_matrix.""" 59 | return self._cal_matrix 60 | 61 | @property 62 | def state_labels(self): 63 | """return the state label ordering of the cal matrix""" 64 | return self._state_labels 65 | 66 | @state_labels.setter 67 | def state_labels(self, new_state_labels): 68 | """set the state label ordering of the cal matrix""" 69 | self._state_labels = new_state_labels 70 | 71 | @cal_matrix.setter 72 | def cal_matrix(self, new_cal_matrix): 73 | """Set cal_matrix.""" 74 | self._cal_matrix = new_cal_matrix 75 | 76 | def apply(self, raw_data, method="least_squares"): 77 | """Apply the calibration matrix to results. 78 | 79 | Args: 80 | raw_data (dict or list): The data to be corrected. Can be in a number of forms: 81 | 82 | Form 1: a counts dictionary from results.get_counts 83 | 84 | Form 2: a list of counts of `length==len(state_labels)` 85 | 86 | I have no idea what this is. I'm not supporting it here. 87 | --> Form 3: a list of counts of `length==M*len(state_labels)` where M is an 88 | integer (e.g. for use with the tomography data) 89 | 90 | Form 4: a qiskit Result 91 | 92 | method (str): fitting method. If `None`, then least_squares is used. 93 | 94 | ``pseudo_inverse``: direct inversion of the A matrix 95 | 96 | ``least_squares``: constrained to have physical probabilities 97 | 98 | Returns: 99 | dict or list: The corrected data in the same form as `raw_data` 100 | 101 | Raises: 102 | QiskitError: if `raw_data` is not an integer multiple 103 | of the number of calibrated states. 104 | 105 | """ 106 | 107 | raw_data = deepcopy(raw_data) 108 | 109 | output_type = None 110 | if isinstance(raw_data, qiskit.result.result.Result): 111 | output_Result = deepcopy(raw_data) 112 | output_type = "result" 113 | raw_data = raw_data.get_counts() 114 | if isinstance(raw_data, dict): 115 | raw_data = [raw_data] 116 | 117 | elif isinstance(raw_data, list): 118 | output_type = "list" 119 | 120 | elif isinstance(raw_data, dict): 121 | raw_data = [raw_data] 122 | output_type = "dict" 123 | 124 | assert output_type 125 | 126 | unique_data_labels = {key for data_row in raw_data for key in data_row.keys()} 127 | if not unique_data_labels.issubset(set(self._state_labels)): 128 | raise QiskitError( 129 | "Unexpected state label '" 130 | + unique_data_labels 131 | + "', verify the fitter's state labels correpsond to the input data" 132 | ) 133 | 134 | raw_data_array = np.zeros((len(raw_data), len(self._state_labels)), dtype=float) 135 | corrected_data_array = np.zeros( 136 | (len(raw_data), len(self._state_labels)), dtype=float 137 | ) 138 | 139 | for expt_idx, data_row in enumerate(raw_data): 140 | for stateidx, state in enumerate(self._state_labels): 141 | raw_data_array[expt_idx][stateidx] = data_row.get(state, 0) 142 | 143 | if method == "pseudo_inverse": 144 | pinv_cal_mat = la.pinv(self._cal_matrix) 145 | 146 | # pylint: disable=unused-variable 147 | corrected_data = np.einsum("ij,xj->xi", pinv_cal_mat, raw_data_array) 148 | 149 | elif method == "least_squares": 150 | 151 | nshots_each_expt = np.sum(raw_data_array, axis=1) 152 | 153 | for expt_idx, (nshots, raw_data_row) in enumerate( 154 | zip(nshots_each_expt, raw_data_array) 155 | ): 156 | cal_mat = self._cal_matrix 157 | nlabels = len(raw_data_row) # pylint: disable=unused-variable 158 | 159 | def fun(estimated_corrected_data): 160 | return np.sum( 161 | (raw_data_row - cal_mat.dot(estimated_corrected_data)) ** 2 162 | ) 163 | 164 | def gradient(estimated_corrected_data): 165 | return 2 * ( 166 | cal_mat.dot(estimated_corrected_data) - raw_data_row 167 | ).dot(cal_mat) 168 | 169 | cons = { 170 | "type": "eq", 171 | "fun": lambda x: nshots - np.sum(x), 172 | "jac": lambda x: -1 * np.ones_like(x), 173 | } 174 | bnds = tuple((0, nshots) for x in raw_data_row) 175 | res = minimize( 176 | fun, 177 | raw_data_row, 178 | method="SLSQP", 179 | constraints=cons, 180 | bounds=bnds, 181 | tol=1e-6, 182 | jac=gradient, 183 | ) 184 | 185 | # def fun(angles): 186 | # # for bounding between 0 and 1 187 | # cos2 = np.cos(angles)**2 188 | # # form should constrain so sum always = nshots. 189 | # estimated_corrected_data = nshots * \ 190 | # (1/nlabels + (nlabels*cos2 - 191 | # np.sum(cos2))/(nlabels-1)) 192 | # return np.sum( (raw_data_row - 193 | # cal_mat.dot(estimated_corrected_data) )**2) 194 | # 195 | # def gradient(estimated_corrected_data): 196 | # return 2 * (cal_mat.dot(estimated_corrected_data) - 197 | # raw_data_row).dot(cal_mat) 198 | # 199 | # bnds = tuple((0, nshots) for x in raw_data_this_idx) 200 | # res = minimize(fun, raw_data_row, 201 | # method='SLSQP', constraints=cons, 202 | # bounds=bnds, tol=1e-6, jac=gradient) 203 | 204 | corrected_data_array[expt_idx] = res.x 205 | 206 | else: 207 | raise QiskitError("Unrecognized method.") 208 | 209 | # time_finished_correction = time.time() 210 | 211 | # convert back into a counts dictionary 212 | 213 | corrected_dicts = [] 214 | for corrected_data_row in corrected_data_array: 215 | new_count_dict = {} 216 | for stateidx, state in enumerate(self._state_labels): 217 | if corrected_data_row[stateidx] != 0: 218 | new_count_dict[state] = corrected_data_row[stateidx] 219 | 220 | corrected_dicts.append(new_count_dict) 221 | 222 | if output_type == "dict": 223 | assert len(corrected_dicts) == 1 224 | # converting back to a single counts dict, to match input provided by user 225 | output = corrected_dicts[0] 226 | elif output_type == "list": 227 | output = corrected_dicts 228 | elif output_type == "result": 229 | for resultidx, new_counts in enumerate(corrected_dicts): 230 | output_Result.results[resultidx].data.counts = new_counts 231 | output = output_Result 232 | else: 233 | raise TypeError() 234 | 235 | return output 236 | 237 | 238 | class TensoredFilter: 239 | """ 240 | Tensored measurement error mitigation filter. 241 | 242 | Produced from a tensored measurement calibration fitter and can be applied 243 | to data. 244 | """ 245 | 246 | def __init__(self, cal_matrices: np.matrix, substate_labels_list: list): 247 | """ 248 | Initialize a tensored measurement error mitigation filter using 249 | the cal_matrices from a tensored measurement calibration fitter. 250 | 251 | Args: 252 | cal_matrices: the calibration matrices for applying the correction. 253 | substate_labels_list: for each calibration matrix 254 | a list of the states (as strings, states in the subspace) 255 | """ 256 | 257 | self._cal_matrices = cal_matrices 258 | self._qubit_list_sizes = [] 259 | self._indices_list = [] 260 | self._substate_labels_list = [] 261 | self.substate_labels_list = substate_labels_list 262 | 263 | @property 264 | def cal_matrices(self): 265 | """Return cal_matrices.""" 266 | return self._cal_matrices 267 | 268 | @cal_matrices.setter 269 | def cal_matrices(self, new_cal_matrices): 270 | """Set cal_matrices.""" 271 | self._cal_matrices = deepcopy(new_cal_matrices) 272 | 273 | @property 274 | def substate_labels_list(self): 275 | """Return _substate_labels_list""" 276 | return self._substate_labels_list 277 | 278 | @substate_labels_list.setter 279 | def substate_labels_list(self, new_substate_labels_list): 280 | """Return _substate_labels_list""" 281 | self._substate_labels_list = new_substate_labels_list 282 | 283 | # get the number of qubits in each subspace 284 | self._qubit_list_sizes = [] 285 | for _, substate_label_list in enumerate(self._substate_labels_list): 286 | self._qubit_list_sizes.append(int(np.log2(len(substate_label_list)))) 287 | 288 | # get the indices in the calibration matrix 289 | self._indices_list = [] 290 | for _, sub_labels in enumerate(self._substate_labels_list): 291 | self._indices_list.append({lab: ind for ind, lab in enumerate(sub_labels)}) 292 | 293 | @property 294 | def qubit_list_sizes(self): 295 | """Return _qubit_list_sizes.""" 296 | return self._qubit_list_sizes 297 | 298 | @property 299 | def nqubits(self): 300 | """Return the number of qubits. See also MeasurementFilter.apply()""" 301 | return sum(self._qubit_list_sizes) 302 | 303 | def apply(self, raw_data, method="least_squares"): 304 | """ 305 | Apply the calibration matrices to results. 306 | 307 | Args: 308 | raw_data (dict or Result): The data to be corrected. Can be in one of two forms: 309 | 310 | * A counts dictionary from results.get_counts 311 | 312 | * A Qiskit Result 313 | 314 | method (str): fitting method. The following methods are supported: 315 | 316 | * 'pseudo_inverse': direct inversion of the cal matrices. 317 | 318 | * 'least_squares': constrained to have physical probabilities. 319 | 320 | * If `None`, 'least_squares' is used. 321 | 322 | Returns: 323 | dict or Result: The corrected data in the same form as raw_data 324 | 325 | Raises: 326 | QiskitError: if raw_data is not in a one of the defined forms. 327 | """ 328 | 329 | all_states = count_keys(self.nqubits) 330 | num_of_states = 2**self.nqubits 331 | 332 | # check forms of raw_data 333 | if isinstance(raw_data, dict): 334 | # counts dictionary 335 | # convert to list 336 | raw_data2 = [np.zeros(num_of_states, dtype=float)] 337 | for state, count in raw_data.items(): 338 | stateidx = int(state, 2) 339 | raw_data2[0][stateidx] = count 340 | 341 | elif isinstance(raw_data, qiskit.result.result.Result): 342 | 343 | # extract out all the counts, re-call the function with the 344 | # counts and push back into the new result 345 | new_result = deepcopy(raw_data) 346 | 347 | new_counts_list = parallel_map( 348 | self._apply_correction, 349 | [resultidx for resultidx, _ in enumerate(raw_data.results)], 350 | task_args=(raw_data, method), 351 | ) 352 | 353 | for resultidx, new_counts in new_counts_list: 354 | new_result.results[resultidx].data.counts = new_counts 355 | 356 | return new_result 357 | 358 | else: 359 | raise QiskitError("Unrecognized type for raw_data.") 360 | 361 | if method == "pseudo_inverse": 362 | pinv_cal_matrices = [] 363 | for cal_mat in self._cal_matrices: 364 | pinv_cal_matrices.append(la.pinv(cal_mat)) 365 | 366 | # Apply the correction 367 | for data_idx, _ in enumerate(raw_data2): 368 | 369 | if method == "pseudo_inverse": 370 | inv_mat_dot_raw = np.zeros([num_of_states], dtype=float) 371 | for state1_idx, state1 in enumerate(all_states): 372 | for state2_idx, state2 in enumerate(all_states): 373 | if raw_data2[data_idx][state2_idx] == 0: 374 | continue 375 | 376 | product = 1.0 377 | end_index = self.nqubits 378 | for p_ind, pinv_mat in enumerate(pinv_cal_matrices): 379 | 380 | start_index = end_index - self._qubit_list_sizes[p_ind] 381 | 382 | state1_as_int = self._indices_list[p_ind][ 383 | state1[start_index:end_index] 384 | ] 385 | 386 | state2_as_int = self._indices_list[p_ind][ 387 | state2[start_index:end_index] 388 | ] 389 | 390 | end_index = start_index 391 | product *= pinv_mat[state1_as_int][state2_as_int] 392 | if product == 0: 393 | break 394 | inv_mat_dot_raw[state1_idx] += ( 395 | product * raw_data2[data_idx][state2_idx] 396 | ) 397 | raw_data2[data_idx] = inv_mat_dot_raw 398 | 399 | elif method == "least_squares": 400 | 401 | def fun(x): 402 | mat_dot_x = np.zeros([num_of_states], dtype=float) 403 | for state1_idx, state1 in enumerate(all_states): 404 | mat_dot_x[state1_idx] = 0.0 405 | for state2_idx, state2 in enumerate(all_states): 406 | if x[state2_idx] != 0: 407 | product = 1.0 408 | end_index = self.nqubits 409 | for c_ind, cal_mat in enumerate(self._cal_matrices): 410 | 411 | start_index = ( 412 | end_index - self._qubit_list_sizes[c_ind] 413 | ) 414 | 415 | state1_as_int = self._indices_list[c_ind][ 416 | state1[start_index:end_index] 417 | ] 418 | 419 | state2_as_int = self._indices_list[c_ind][ 420 | state2[start_index:end_index] 421 | ] 422 | 423 | end_index = start_index 424 | product *= cal_mat[state1_as_int][state2_as_int] 425 | if product == 0: 426 | break 427 | mat_dot_x[state1_idx] += product * x[state2_idx] 428 | return sum((raw_data2[data_idx] - mat_dot_x) ** 2) 429 | 430 | x0 = np.random.rand(num_of_states) 431 | x0 = x0 / sum(x0) 432 | nshots = sum(raw_data2[data_idx]) 433 | cons = {"type": "eq", "fun": lambda x: nshots - sum(x)} 434 | bnds = tuple((0, nshots) for x in x0) 435 | res = minimize( 436 | fun, x0, method="SLSQP", constraints=cons, bounds=bnds, tol=1e-6 437 | ) 438 | raw_data2[data_idx] = res.x 439 | 440 | else: 441 | raise QiskitError("Unrecognized method.") 442 | 443 | # convert back into a counts dictionary 444 | new_count_dict = {} 445 | for state_idx, state in enumerate(all_states): 446 | if raw_data2[0][state_idx] != 0: 447 | new_count_dict[state] = raw_data2[0][state_idx] 448 | 449 | return new_count_dict 450 | 451 | def _apply_correction(self, resultidx, raw_data, method): 452 | """Wrapper to call apply with a counts dictionary.""" 453 | new_counts = self.apply(raw_data.get_counts(resultidx), method=method) 454 | return resultidx, new_counts 455 | -------------------------------------------------------------------------------- /entanglement_forging/utils/prepare_bitstring.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """Bitstring functions.""" 14 | from qiskit import QuantumCircuit 15 | 16 | 17 | def prepare_bitstring(bitstring, name=None): 18 | """Prepares bitstrings.""" 19 | # First bit in bitstring is the first qubit in the circuit. 20 | qcirc = QuantumCircuit(len(bitstring), name=name) 21 | for qb_idx, bit in enumerate(bitstring): 22 | if bit: 23 | qcirc.x(qb_idx) 24 | return qcirc 25 | -------------------------------------------------------------------------------- /entanglement_forging/utils/pseudorichardson.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """Pseudo Richardson circuits module.""" 14 | import numpy as np 15 | 16 | 17 | def make_pseudorichardson_circuits(transpiled_circuits, simple_richardson_orders=None): 18 | """Creates pseudo Richardson circuits. 19 | 20 | Args: 21 | transpiled_circuits: Transpiled circuits 22 | simple_richardson_orders: Richardson orders 23 | 24 | Returns: 25 | List of pseudo Richardson circuits. 26 | """ 27 | if simple_richardson_orders is None: 28 | simple_richardson_orders = [0] 29 | basic_insts = ["measure", "reset", "barrier", "snapshot"] 30 | final_circuits = [] 31 | for qcirc in transpiled_circuits: 32 | for order in simple_richardson_orders: 33 | stretch_factor = 2 * (order) + 1 34 | name_parts = qcirc.name.split("_") 35 | new_qc = qcirc.copy( 36 | name="_".join( 37 | name_parts[:-1] 38 | + [f"richardson{stretch_factor:.2f}", name_parts[-1]] 39 | ) 40 | ) 41 | new_qc._data = [] # pylint: disable=protected-access 42 | # already handled in qcirc.copy()? 43 | new_qc._layout = qcirc._layout # pylint: disable=protected-access 44 | for instr in qcirc.data: 45 | (operator, qargs, cargs) = instr 46 | new_qc.append(operator, qargs=qargs, cargs=cargs) 47 | if order == 0 or operator.name in basic_insts: 48 | continue 49 | if operator.name in ["delay"]: 50 | op_inv = operator 51 | else: 52 | op_inv = operator.inverse() 53 | for _ in range(order): 54 | new_qc.barrier(qargs) 55 | # pylint: disable=expression-not-assigned 56 | if operator.name == "sx": 57 | [new_qc.rz(np.pi, q) for q in qargs] 58 | [new_qc.sx(q) for q in qargs] 59 | [new_qc.rz(np.pi, q) for q in qargs] 60 | else: 61 | new_qc.append(op_inv, qargs=qargs, cargs=cargs) 62 | # pylint: enable=expression-not-assigned 63 | new_qc.barrier(*qargs) 64 | new_qc.append(operator, qargs=qargs, cargs=cargs) 65 | final_circuits.append(new_qc) 66 | return final_circuits 67 | 68 | 69 | # pylint: disable=too-many-locals 70 | def richardson_extrapolate(ydata, stretch_factors, axis, max_polyfit_degree=None): 71 | """Richardson extrapolation. 72 | 73 | Args: 74 | ydata: a numpy array of expectation values acquired at different stretch factors. 75 | The last axis of ydata should have length 2, 76 | with the 0th element being the expectation value, 77 | and the next element being the standard deviation. 78 | stretch_factors: 79 | axis: which axis of ydata corresponds to the stretch_factors. 80 | max_polyfit_degree: Extrapolation will proceed by (effectively) fitting 81 | a polynomial to the results from the different stretch factors. 82 | If max_polyfit_degree is None, this polynomial is degree len(stretch_factors)-1, 83 | so e.g. if there are only 2 stretch factors then a linear fit is used. 84 | If max_polyfit_degree is an integer less than len(stretch_factors)-1, 85 | the polynomial will be constrained to that degree. 86 | E.g. if you have 3 stretch factors but wanted to fit just a line, 87 | set max_polyfit_degree = 1. 88 | 89 | 90 | Returns: 91 | ydata_corrected: an array with shape like ydata but with the axis 'axis' eliminated. 92 | """ 93 | 94 | polyfit_degree = len(stretch_factors) - 1 95 | if max_polyfit_degree: 96 | polyfit_degree = min(polyfit_degree, max_polyfit_degree) 97 | 98 | indexing_each_axis = [slice(None)] * ydata.ndim 99 | if polyfit_degree == 0: 100 | indexing_each_axis[axis] = 0 101 | ydata_corrected = ydata[tuple(indexing_each_axis)] 102 | elif polyfit_degree == 1 and len(stretch_factors) == 2: 103 | # faster/vectorized implementation of the case I care about 104 | # right now (TODO: generalize this) instead of calling curve_fit/polyfit: # pylint: disable=fixme 105 | stretch1, stretch2 = stretch_factors 106 | denom = stretch2 - stretch1 107 | indexing_each_axis[-1] = 0 108 | indexing_each_axis[axis] = 0 109 | y1 = ydata[tuple(indexing_each_axis)] # pylint: disable=invalid-name 110 | indexing_each_axis[axis] = 1 111 | y2 = ydata[tuple(indexing_each_axis)] # pylint: disable=invalid-name 112 | indexing_each_axis[-1] = 1 113 | indexing_each_axis[axis] = 0 114 | var1 = ydata[tuple(indexing_each_axis)] 115 | indexing_each_axis[axis] = 1 116 | var2 = ydata[tuple(indexing_each_axis)] 117 | y_extrap = ( 118 | y1 * stretch2 - y2 * stretch1 119 | ) / denom # pylint: disable=invalid-name 120 | var_extrap = var1 * (stretch2 / denom) ** 2 + var2 * (stretch1 / denom) ** 2 121 | ydata_corrected = np.stack([y_extrap, var_extrap], axis=-1) 122 | else: 123 | raise NotImplementedError( 124 | "TODO: implement general Richardson extrapolation using numpy Polynomial.fit() " 125 | "inside loop, or else (maybe better?) vectorized general matrix equation" 126 | ) 127 | 128 | return ydata_corrected 129 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=40.9.0", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = entanglement_forging 3 | version = 0.2.1 4 | url = https://github.com/qiskit-community/prototype-entanglement-forging 5 | maintainer = Caleb Johnson 6 | maintainer_email = caleb.johnson@ibm.com 7 | classifiers = 8 | Development Status :: 4 - Beta 9 | Intended Audience :: Developers 10 | Intended Audience :: Education 11 | Intended Audience :: Science/Research 12 | License :: OSI Approved :: Apache Software License 13 | Natural Language :: English 14 | Operating System :: MacOS 15 | Operating System :: POSIX :: Linux 16 | Programming Language :: Python :: 3.7 17 | Programming Language :: Python :: 3.8 18 | Programming Language :: Python :: 3.9 19 | Topic :: Scientific/Engineering :: Chemistry 20 | Topic :: Scientific/Engineering :: Physics 21 | license = Apache 2.0 22 | description = Simulate chemical and physical systems using entanglement forging. 23 | long_description = This module allows a user to simulate chemical and physical systems using a variational quantum eigensolver enhanced by entanglement forging. Entanglement forging doubles the size of the system that can be exactly simulated on a fixed set of quantum bits. 24 | keywords = quantum, nature, simulation 25 | 26 | 27 | [options] 28 | packages = find: 29 | install_requires= 30 | qiskit==0.35.0 31 | qiskit_nature==0.3.2 32 | pyscf==2.0.1 33 | h5py==3.6.0 34 | matplotlib==3.5.1 35 | ipywidgets==7.6.5 36 | jupyter 37 | 38 | [options.extras_require] 39 | dev = 40 | build 41 | pytest 42 | treon 43 | tox 44 | click==8.0.4 45 | black==22.3.0 46 | test = 47 | treon 48 | click==8.0.4 49 | black==22.3.0 50 | pytest 51 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | -------------------------------------------------------------------------------- /tests/unit_tests/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | -------------------------------------------------------------------------------- /tests/unit_tests/test_orbitals_to_reduce.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """Unit tests for OrbitalsToReduce object.""" 14 | import unittest 15 | 16 | import numpy as np 17 | from qiskit_nature.drivers.second_quantization import PySCFDriver 18 | from qiskit_nature.drivers import Molecule 19 | from qiskit_nature.problems.second_quantization import ElectronicStructureProblem 20 | 21 | from entanglement_forging import OrbitalsToReduce 22 | 23 | 24 | class TestEntanglementForgedGroundStateEigensolver(unittest.TestCase): 25 | """EntanglementForgedGroundStateEigensolver tests.""" 26 | 27 | def test_orbitals_to_reduce_water_all(self): 28 | """Test for when we have both occupied and virtual orbitals.""" 29 | # setup problem 30 | radius_1 = 0.958 # position for the first H atom 31 | radius_2 = 0.958 # position for the second H atom 32 | thetas_in_deg = 104.478 # bond angles. 33 | 34 | hydrogen_1_x_coord = radius_1 35 | hydrogen_2_x_coord = radius_2 * np.cos(np.pi / 180 * thetas_in_deg) 36 | hydrogen_2_y_coord = radius_2 * np.sin(np.pi / 180 * thetas_in_deg) 37 | 38 | molecule = Molecule( 39 | geometry=[ 40 | ["O", [0.0, 0.0, 0.0]], 41 | ["H", [hydrogen_1_x_coord, 0.0, 0.0]], 42 | ["H", [hydrogen_2_x_coord, hydrogen_2_y_coord, 0.0]], 43 | ], 44 | charge=0, 45 | multiplicity=1, 46 | ) 47 | driver = PySCFDriver.from_molecule(molecule) 48 | problem = ElectronicStructureProblem(driver) 49 | problem.second_q_ops() 50 | 51 | all_orbitals_to_reduce = [0, 1, 2, 3, 4, 5, 6, 7, 8] 52 | 53 | # solution 54 | orbitals_to_reduce = OrbitalsToReduce(all_orbitals_to_reduce, problem) 55 | self.assertEqual(orbitals_to_reduce.occupied(), [0, 1, 2, 3, 4]) 56 | self.assertEqual(orbitals_to_reduce.virtual(), [5, 6, 7, 8]) 57 | 58 | def test_orbitals_to_reduce_water_occupied(self): 59 | """Test for when we have only occupied orbitals.""" 60 | # setup problem 61 | radius_1 = 0.958 # position for the first H atom 62 | radius_2 = 0.958 # position for the second H atom 63 | thetas_in_deg = 104.478 # bond angles. 64 | 65 | hydrogen_1_x_coord = radius_1 66 | hydrogen_2_x_coord = radius_2 * np.cos(np.pi / 180 * thetas_in_deg) 67 | hydrogen_2_y_coord = radius_2 * np.sin(np.pi / 180 * thetas_in_deg) 68 | 69 | molecule = Molecule( 70 | geometry=[ 71 | ["O", [0.0, 0.0, 0.0]], 72 | ["H", [hydrogen_1_x_coord, 0.0, 0.0]], 73 | ["H", [hydrogen_2_x_coord, hydrogen_2_y_coord, 0.0]], 74 | ], 75 | charge=0, 76 | multiplicity=1, 77 | ) 78 | driver = PySCFDriver.from_molecule(molecule) 79 | problem = ElectronicStructureProblem(driver) 80 | problem.second_q_ops() 81 | 82 | all_orbitals_to_reduce = [0, 2, 4] 83 | 84 | # solution 85 | orbitals_to_reduce = OrbitalsToReduce(all_orbitals_to_reduce, problem) 86 | self.assertEqual(orbitals_to_reduce.occupied(), [0, 2, 4]) 87 | self.assertFalse(orbitals_to_reduce.virtual()) 88 | 89 | def test_orbitals_to_reduce_water_virtual(self): 90 | """Test for when we have only virtual orbitals.""" 91 | # setup problem 92 | radius_1 = 0.958 # position for the first H atom 93 | radius_2 = 0.958 # position for the second H atom 94 | thetas_in_deg = 104.478 # bond angles. 95 | 96 | hydrogen_1_x_coord = radius_1 97 | hydrogen_2_x_coord = radius_2 * np.cos(np.pi / 180 * thetas_in_deg) 98 | hydrogen_2_y_coord = radius_2 * np.sin(np.pi / 180 * thetas_in_deg) 99 | 100 | molecule = Molecule( 101 | geometry=[ 102 | ["O", [0.0, 0.0, 0.0]], 103 | ["H", [hydrogen_1_x_coord, 0.0, 0.0]], 104 | ["H", [hydrogen_2_x_coord, hydrogen_2_y_coord, 0.0]], 105 | ], 106 | charge=0, 107 | multiplicity=1, 108 | ) 109 | driver = PySCFDriver.from_molecule(molecule) 110 | problem = ElectronicStructureProblem(driver) 111 | problem.second_q_ops() 112 | 113 | all_orbitals_to_reduce = [6, 7, 9] 114 | 115 | # solution 116 | orbitals_to_reduce = OrbitalsToReduce(all_orbitals_to_reduce, problem) 117 | self.assertFalse(orbitals_to_reduce.occupied()) 118 | self.assertEqual(orbitals_to_reduce.virtual(), [6, 7, 9]) 119 | -------------------------------------------------------------------------------- /tests/wrappers/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | -------------------------------------------------------------------------------- /tests/wrappers/test_data/CH3_one_body.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/prototype-entanglement-forging/c10fa9421b23412d5e0bee3632f0c15cb3d45764/tests/wrappers/test_data/CH3_one_body.npy -------------------------------------------------------------------------------- /tests/wrappers/test_data/CH3_two_body.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/prototype-entanglement-forging/c10fa9421b23412d5e0bee3632f0c15cb3d45764/tests/wrappers/test_data/CH3_two_body.npy -------------------------------------------------------------------------------- /tests/wrappers/test_data/CN_one_body.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/prototype-entanglement-forging/c10fa9421b23412d5e0bee3632f0c15cb3d45764/tests/wrappers/test_data/CN_one_body.npy -------------------------------------------------------------------------------- /tests/wrappers/test_data/CN_two_body.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/prototype-entanglement-forging/c10fa9421b23412d5e0bee3632f0c15cb3d45764/tests/wrappers/test_data/CN_two_body.npy -------------------------------------------------------------------------------- /tests/wrappers/test_data/O2_one_body.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/prototype-entanglement-forging/c10fa9421b23412d5e0bee3632f0c15cb3d45764/tests/wrappers/test_data/O2_one_body.npy -------------------------------------------------------------------------------- /tests/wrappers/test_data/O2_two_body.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/prototype-entanglement-forging/c10fa9421b23412d5e0bee3632f0c15cb3d45764/tests/wrappers/test_data/O2_two_body.npy -------------------------------------------------------------------------------- /tests/wrappers/test_data/OH_one_body.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/prototype-entanglement-forging/c10fa9421b23412d5e0bee3632f0c15cb3d45764/tests/wrappers/test_data/OH_one_body.npy -------------------------------------------------------------------------------- /tests/wrappers/test_data/OH_two_body.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/prototype-entanglement-forging/c10fa9421b23412d5e0bee3632f0c15cb3d45764/tests/wrappers/test_data/OH_two_body.npy -------------------------------------------------------------------------------- /tests/wrappers/test_data/TS_one_body.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/prototype-entanglement-forging/c10fa9421b23412d5e0bee3632f0c15cb3d45764/tests/wrappers/test_data/TS_one_body.npy -------------------------------------------------------------------------------- /tests/wrappers/test_data/TS_two_body.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/prototype-entanglement-forging/c10fa9421b23412d5e0bee3632f0c15cb3d45764/tests/wrappers/test_data/TS_two_body.npy -------------------------------------------------------------------------------- /tests/wrappers/test_data/two_body_alpha_alpha.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/prototype-entanglement-forging/c10fa9421b23412d5e0bee3632f0c15cb3d45764/tests/wrappers/test_data/two_body_alpha_alpha.npy -------------------------------------------------------------------------------- /tests/wrappers/test_entanglement_forged_ground_state_eigensolver.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """Integration tests for EntanglementForgedVQE module.""" 14 | # pylint: disable=wrong-import-position 15 | import unittest 16 | import os 17 | 18 | import numpy as np 19 | from qiskit import BasicAer 20 | from qiskit.circuit import Parameter, QuantumCircuit 21 | from qiskit.circuit.library import TwoLocal 22 | from qiskit_nature.converters.second_quantization import QubitConverter 23 | from qiskit_nature.drivers import Molecule 24 | from qiskit_nature.drivers.second_quantization import PySCFDriver 25 | from qiskit_nature.mappers.second_quantization import JordanWignerMapper 26 | from qiskit_nature.problems.second_quantization import ElectronicStructureProblem 27 | from qiskit_nature.algorithms.ground_state_solvers import ( 28 | GroundStateEigensolver, 29 | NumPyMinimumEigensolverFactory, 30 | ) 31 | from qiskit_nature.transformers.second_quantization.electronic.active_space_transformer import ( 32 | ActiveSpaceTransformer, 33 | ) 34 | from qiskit_nature import settings 35 | 36 | settings.dict_aux_operators = True 37 | 38 | from entanglement_forging import reduce_bitstrings 39 | from entanglement_forging import ( 40 | EntanglementForgedConfig, 41 | EntanglementForgedDriver, 42 | EntanglementForgedGroundStateSolver, 43 | ) 44 | 45 | 46 | class TestEntanglementForgedGroundStateEigensolver(unittest.TestCase): 47 | """EntanglementForgedGroundStateEigensolver tests.""" 48 | 49 | def setUp(self): 50 | np.random.seed(42) 51 | self.backend = BasicAer.get_backend("statevector_simulator") 52 | self.config = EntanglementForgedConfig( 53 | backend=self.backend, 54 | maxiter=0, 55 | initial_params=[0.0], 56 | optimizer_name="COBYLA", 57 | ) 58 | 59 | # TS 60 | self.mock_ts_ansatz = self.create_mock_ansatz(4) 61 | self.hcore_ts = np.load( 62 | os.path.join(os.path.dirname(__file__), "test_data", "TS_one_body.npy") 63 | ) 64 | self.eri_ts = np.load( 65 | os.path.join(os.path.dirname(__file__), "test_data", "TS_two_body.npy") 66 | ) 67 | self.energy_shift_ts = -264.7518219120776 68 | 69 | # O2 70 | self.mock_o2_ansatz = self.create_mock_ansatz(8) 71 | self.hcore_o2 = np.load( 72 | os.path.join(os.path.dirname(__file__), "test_data", "O2_one_body.npy") 73 | ) 74 | self.eri_o2 = np.load( 75 | os.path.join(os.path.dirname(__file__), "test_data", "O2_two_body.npy") 76 | ) 77 | self.energy_shift_o2 = -99.83894101027317 78 | 79 | # CH3 80 | self.mock_ch3_ansatz = self.create_mock_ansatz(6) 81 | self.hcore_ch3 = np.load( 82 | os.path.join(os.path.dirname(__file__), "test_data", "CH3_one_body.npy") 83 | ) 84 | self.eri_ch3 = np.load( 85 | os.path.join(os.path.dirname(__file__), "test_data", "CH3_two_body.npy") 86 | ) 87 | self.energy_shift_ch3 = -31.90914780401554 88 | 89 | def create_mock_ansatz(self, num_qubits): 90 | n_theta = 1 91 | theta = Parameter("θ") 92 | mock_gate = QuantumCircuit(1, name="mock gate") 93 | mock_gate.rz(theta, 0) 94 | 95 | theta_vec = [Parameter("θ%d" % i) for i in range(1)] 96 | ansatz = QuantumCircuit(num_qubits) 97 | ansatz.append(mock_gate.to_gate({theta: theta_vec[0]}), [0]) 98 | 99 | return ansatz 100 | 101 | def test_forged_vqe_H2(self): 102 | """Test of applying Entanglement Forged VQE to to compute the energy of a H2 molecule.""" 103 | # setup problem 104 | molecule = Molecule( 105 | geometry=[("H", [0.0, 0.0, 0.0]), ("H", [0.0, 0.0, 0.735])], 106 | charge=0, 107 | multiplicity=1, 108 | ) 109 | driver = PySCFDriver.from_molecule(molecule) 110 | problem = ElectronicStructureProblem(driver) 111 | problem.second_q_ops() 112 | 113 | # solution 114 | bitstrings = [[1, 0], [0, 1]] 115 | ansatz = TwoLocal(2, [], "cry", [[0, 1], [1, 0]], reps=1) 116 | 117 | config = EntanglementForgedConfig( 118 | backend=self.backend, maxiter=0, initial_params=[0, 0.5 * np.pi] 119 | ) 120 | 121 | converter = QubitConverter(JordanWignerMapper()) 122 | 123 | forged_ground_state_solver = EntanglementForgedGroundStateSolver( 124 | converter, ansatz, bitstrings, config 125 | ) 126 | 127 | forged_result = forged_ground_state_solver.solve(problem) 128 | 129 | self.assertAlmostEqual(forged_result.ground_state_energy, -1.1219365445030705) 130 | 131 | def test_forged_vqe_H2O(self): # pylint: disable=too-many-locals 132 | """Test of applying Entanglement Forged VQE to to compute the energy of a H20 molecule.""" 133 | # setup problem 134 | radius_1 = 0.958 # position for the first H atom 135 | radius_2 = 0.958 # position for the second H atom 136 | thetas_in_deg = 104.478 # bond angles. 137 | 138 | h1_x = radius_1 139 | h2_x = radius_2 * np.cos(np.pi / 180 * thetas_in_deg) 140 | h2_y = radius_2 * np.sin(np.pi / 180 * thetas_in_deg) 141 | 142 | molecule = Molecule( 143 | geometry=[ 144 | ("O", [0.0, 0.0, 0.0]), 145 | ("H", [h1_x, 0.0, 0.0]), 146 | ("H", [h2_x, h2_y, 0.0]), 147 | ], 148 | charge=0, 149 | multiplicity=1, 150 | ) 151 | driver = PySCFDriver.from_molecule(molecule, basis="sto6g") 152 | problem = ElectronicStructureProblem(driver) 153 | problem.second_q_ops() 154 | 155 | # solution 156 | orbitals_to_reduce = [0, 3] 157 | bitstrings = [ 158 | [1, 1, 1, 1, 1, 0, 0], 159 | [1, 0, 1, 1, 1, 0, 1], 160 | [1, 0, 1, 1, 1, 1, 0], 161 | ] 162 | reduced_bitstrings = reduce_bitstrings(bitstrings, orbitals_to_reduce) 163 | 164 | theta = Parameter("θ") 165 | theta_1, theta_2, theta_3, theta_4 = ( 166 | Parameter("θ1"), 167 | Parameter("θ2"), 168 | Parameter("θ3"), 169 | Parameter("θ4"), 170 | ) 171 | 172 | hop_gate = QuantumCircuit(2, name="Hop gate") 173 | hop_gate.h(0) 174 | hop_gate.cx(1, 0) 175 | hop_gate.cx(0, 1) 176 | hop_gate.ry(-theta, 0) 177 | hop_gate.ry(-theta, 1) 178 | hop_gate.cx(0, 1) 179 | hop_gate.h(0) 180 | 181 | ansatz = QuantumCircuit(5) 182 | ansatz.append(hop_gate.to_gate({theta: theta_1}), [0, 1]) 183 | ansatz.append(hop_gate.to_gate({theta: theta_2}), [3, 4]) 184 | ansatz.append(hop_gate.to_gate({theta: 0}), [1, 4]) 185 | ansatz.append(hop_gate.to_gate({theta: theta_3}), [0, 2]) 186 | ansatz.append(hop_gate.to_gate({theta: theta_4}), [3, 4]) 187 | 188 | config = EntanglementForgedConfig( 189 | backend=self.backend, 190 | maxiter=0, 191 | spsa_c0=20 * np.pi, 192 | initial_params=[0, 0, 0, 0], 193 | ) 194 | 195 | converter = QubitConverter(JordanWignerMapper()) 196 | 197 | solver = EntanglementForgedGroundStateSolver( 198 | converter, 199 | ansatz, 200 | reduced_bitstrings, 201 | config, 202 | orbitals_to_reduce=orbitals_to_reduce, 203 | ) 204 | forged_result = solver.solve(problem) 205 | self.assertAlmostEqual(forged_result.ground_state_energy, -75.68366174497027) 206 | 207 | def test_ef_driver(self): 208 | """Test for entanglement forging driver.""" 209 | hcore = np.array([[-1.12421758, -0.9652574], [-0.9652574, -1.12421758]]) 210 | mo_coeff = np.array([[0.54830202, 1.21832731], [0.54830202, -1.21832731]]) 211 | eri = np.array( 212 | [ 213 | [ 214 | [[0.77460594, 0.44744572], [0.44744572, 0.57187698]], 215 | [[0.44744572, 0.3009177], [0.3009177, 0.44744572]], 216 | ], 217 | [ 218 | [[0.44744572, 0.3009177], [0.3009177, 0.44744572]], 219 | [[0.57187698, 0.44744572], [0.44744572, 0.77460594]], 220 | ], 221 | ] 222 | ) 223 | 224 | driver = EntanglementForgedDriver( 225 | hcore=hcore, 226 | mo_coeff=mo_coeff, 227 | eri=eri, 228 | num_alpha=1, 229 | num_beta=1, 230 | nuclear_repulsion_energy=0.7199689944489797, 231 | ) 232 | problem = ElectronicStructureProblem(driver) 233 | problem.second_q_ops() 234 | 235 | bitstrings = [[1, 0], [0, 1]] 236 | ansatz = TwoLocal(2, [], "cry", [[0, 1], [1, 0]], reps=1) 237 | 238 | config = EntanglementForgedConfig( 239 | backend=self.backend, maxiter=0, initial_params=[0, 0.5 * np.pi] 240 | ) 241 | converter = QubitConverter(JordanWignerMapper()) 242 | forged_ground_state_solver = EntanglementForgedGroundStateSolver( 243 | converter, ansatz, bitstrings, config 244 | ) 245 | forged_result = forged_ground_state_solver.solve(problem) 246 | self.assertAlmostEqual(forged_result.ground_state_energy, -1.1219365445030705) 247 | 248 | def test_ground_state_eigensolver_with_ef_driver(self): 249 | """Tests standard qiskit nature solver.""" 250 | hcore = np.array([[-1.12421758, -0.9652574], [-0.9652574, -1.12421758]]) 251 | mo_coeff = np.array([[0.54830202, 1.21832731], [0.54830202, -1.21832731]]) 252 | eri = np.array( 253 | [ 254 | [ 255 | [[0.77460594, 0.44744572], [0.44744572, 0.57187698]], 256 | [[0.44744572, 0.3009177], [0.3009177, 0.44744572]], 257 | ], 258 | [ 259 | [[0.44744572, 0.3009177], [0.3009177, 0.44744572]], 260 | [[0.57187698, 0.44744572], [0.44744572, 0.77460594]], 261 | ], 262 | ] 263 | ) 264 | 265 | repulsion_energy = 0.7199689944489797 266 | driver = EntanglementForgedDriver( 267 | hcore=hcore, 268 | mo_coeff=mo_coeff, 269 | eri=eri, 270 | num_alpha=1, 271 | num_beta=1, 272 | nuclear_repulsion_energy=repulsion_energy, 273 | ) 274 | problem = ElectronicStructureProblem(driver) 275 | problem.second_q_ops() 276 | 277 | converter = QubitConverter(JordanWignerMapper()) 278 | solver = GroundStateEigensolver( 279 | converter, 280 | NumPyMinimumEigensolverFactory(use_default_filter_criterion=False), 281 | ) 282 | result = solver.solve(problem) 283 | self.assertAlmostEqual( 284 | -1.137306026563, np.real(result.eigenenergies[0]) + repulsion_energy 285 | ) 286 | 287 | def test_O2_1(self): 288 | driver = EntanglementForgedDriver( 289 | hcore=self.hcore_o2, 290 | mo_coeff=np.eye(8, 8), 291 | eri=self.eri_o2, 292 | num_alpha=6, 293 | num_beta=6, 294 | nuclear_repulsion_energy=self.energy_shift_o2, 295 | ) 296 | problem = ElectronicStructureProblem(driver) 297 | problem.second_q_ops() 298 | 299 | converter = QubitConverter(JordanWignerMapper()) 300 | 301 | bitstrings_u = [ 302 | [1, 1, 1, 1, 1, 1, 0, 0], 303 | [1, 1, 1, 1, 1, 0, 1, 0], 304 | [1, 1, 1, 1, 0, 1, 1, 0], 305 | [1, 1, 0, 1, 1, 1, 1, 0], 306 | ] 307 | bitstrings_v = [ 308 | [1, 1, 1, 1, 1, 0, 1, 0], 309 | [1, 1, 1, 1, 1, 1, 0, 0], 310 | [1, 1, 0, 1, 1, 1, 1, 0], 311 | [1, 1, 1, 1, 0, 1, 1, 0], 312 | ] 313 | 314 | calc = EntanglementForgedGroundStateSolver( 315 | converter, 316 | self.mock_o2_ansatz, 317 | bitstrings_u=bitstrings_u, 318 | bitstrings_v=bitstrings_v, 319 | config=self.config, 320 | orbitals_to_reduce=[], 321 | ) 322 | res = calc.solve(problem) 323 | self.assertAlmostEqual(-147.63645235088566, res.ground_state_energy) 324 | 325 | def test_CH3(self): 326 | driver = EntanglementForgedDriver( 327 | hcore=self.hcore_ch3, 328 | mo_coeff=np.eye(6, 6), 329 | eri=self.eri_ch3, 330 | num_alpha=3, 331 | num_beta=2, 332 | nuclear_repulsion_energy=self.energy_shift_ch3, 333 | ) 334 | 335 | problem = ElectronicStructureProblem(driver) 336 | problem.second_q_ops() 337 | 338 | converter = QubitConverter(JordanWignerMapper()) 339 | 340 | bitstrings_u = [ 341 | [1, 1, 1, 0, 0, 0], 342 | [0, 1, 1, 0, 0, 1], 343 | [1, 0, 1, 0, 1, 0], 344 | [1, 0, 1, 1, 0, 0], 345 | [0, 1, 1, 1, 0, 0], 346 | ] 347 | bitstrings_v = [ 348 | [1, 1, 0, 0, 0, 0], 349 | [0, 1, 0, 0, 0, 1], 350 | [1, 0, 0, 0, 1, 0], 351 | [1, 0, 0, 1, 0, 0], 352 | [0, 1, 0, 1, 0, 0], 353 | ] 354 | 355 | calc = EntanglementForgedGroundStateSolver( 356 | converter, 357 | self.mock_ch3_ansatz, 358 | bitstrings_u=bitstrings_u, 359 | bitstrings_v=bitstrings_v, 360 | config=self.config, 361 | orbitals_to_reduce=[], 362 | ) 363 | res = calc.solve(problem) 364 | self.assertAlmostEqual(-39.09031477502881, res.ground_state_energy) 365 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py{37,38,39} 3 | isolated_build = true 4 | 5 | [testenv] 6 | extras = test 7 | commands = 8 | python -m black --check entanglement_forging tests 9 | python -m pytest tests 10 | python -m treon docs/ 11 | --------------------------------------------------------------------------------