├── .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 | 
10 | [](https://www.python.org/)
11 | [](https://github.com/Qiskit/qiskit)
12 | [](https://github.com/Qiskit/qiskit-nature)
13 |
14 | [](LICENSE.txt)
15 | [](https://github.com/qiskit-community/prototype-entanglement-forging/actions/workflows/tests.yml)
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
Entanglement Forging
24 |
25 |
26 |
27 |
28 |
29 |
30 |
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 | 
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 |
--------------------------------------------------------------------------------