├── LICENSE ├── README.md ├── dist ├── quantum_linear_solvers-0.1.dev1+g17fb9c0.tar.gz ├── quantum_linear_solvers-0.1.dev2+g81a3efb-py3-none-any.whl └── quantum_linear_solvers-0.1.dev2+g81a3efb.tar.gz ├── linear_solvers ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-38.pyc │ ├── __init__.cpython-39.pyc │ ├── hhl.cpython-38.pyc │ ├── hhl.cpython-39.pyc │ ├── linear_solver.cpython-38.pyc │ ├── linear_solver.cpython-39.pyc │ ├── numpy_linear_solver.cpython-38.pyc │ └── numpy_linear_solver.cpython-39.pyc ├── hhl.py ├── linear_solver.py ├── matrices │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-38.pyc │ │ ├── __init__.cpython-39.pyc │ │ ├── linear_system_matrix.cpython-38.pyc │ │ ├── linear_system_matrix.cpython-39.pyc │ │ ├── numpy_matrix.cpython-38.pyc │ │ ├── numpy_matrix.cpython-39.pyc │ │ ├── tridiagonal_toeplitz.cpython-38.pyc │ │ └── tridiagonal_toeplitz.cpython-39.pyc │ ├── linear_system_matrix.py │ ├── numpy_matrix.py │ └── tridiagonal_toeplitz.py ├── numpy_linear_solver.py └── observables │ ├── __init__.py │ ├── __pycache__ │ ├── __init__.cpython-38.pyc │ ├── __init__.cpython-39.pyc │ ├── absolute_average.cpython-38.pyc │ ├── absolute_average.cpython-39.pyc │ ├── linear_system_observable.cpython-38.pyc │ ├── linear_system_observable.cpython-39.pyc │ ├── matrix_functional.cpython-38.pyc │ └── matrix_functional.cpython-39.pyc │ ├── absolute_average.py │ ├── linear_system_observable.py │ └── matrix_functional.py ├── pyproject.toml ├── quantum_linear_solvers.egg-info ├── PKG-INFO ├── SOURCES.txt ├── dependency_links.txt ├── requires.txt └── top_level.txt ├── requirements-dev.txt ├── requirements.txt ├── setup.py └── test ├── __init__.py ├── __pycache__ ├── __init__.cpython-38.pyc └── test_linear_solvers.cpython-38.pyc └── test_linear_solvers.py /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quantum linear solvers 2 | Contains classical and quantum algorithms to solve systems of linear equations such as the HHL algorithm. 3 | 4 | ## Installation 5 | ```python 6 | git clone https://github.com/anedumla/quantum_linear_solvers.git 7 | cd quantum_linear_solvers 8 | pip install . 9 | ``` 10 | 11 | ## Documentation 12 | Tutorial: https://qiskit.org/textbook/ch-applications/hhl_tutorial.html 13 | -------------------------------------------------------------------------------- /dist/quantum_linear_solvers-0.1.dev1+g17fb9c0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/dist/quantum_linear_solvers-0.1.dev1+g17fb9c0.tar.gz -------------------------------------------------------------------------------- /dist/quantum_linear_solvers-0.1.dev2+g81a3efb-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/dist/quantum_linear_solvers-0.1.dev2+g81a3efb-py3-none-any.whl -------------------------------------------------------------------------------- /dist/quantum_linear_solvers-0.1.dev2+g81a3efb.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/dist/quantum_linear_solvers-0.1.dev2+g81a3efb.tar.gz -------------------------------------------------------------------------------- /linear_solvers/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021, 2022. 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 | Linear solvers (:mod:`quantum_linear_solvers.linear_solvers`) 15 | ========================================================= 16 | It contains classical and quantum algorithms to solve systems of linear equations such as 17 | :class:`~quantum_linear_solvers.linear_solvers.HHL`. 18 | Although the quantum algorithm accepts a general Hermitian matrix as input, Qiskit's default 19 | Hamiltonian evolution is exponential in such cases and therefore the quantum linear solver will 20 | not achieve an exponential speedup. 21 | Furthermore, the quantum algorithm can find a solution exponentially faster in the size of the 22 | system than their classical counterparts (i.e. logarithmic complexity instead of polynomial), 23 | meaning that reading the full solution vector would kill such speedup (since this would take 24 | linear time in the size of the system). 25 | Therefore, to achieve an exponential speedup we can only compute functions from the solution 26 | vector (the so called observables) to learn information about the solution. 27 | Known efficient implementations of Hamiltonian evolutions or observables are contained in the 28 | following subfolders: 29 | 30 | `Matrices`_ 31 | A placeholder for efficient implementations of the Hamiltonian evolution of particular types of 32 | matrices. 33 | 34 | `Observables`_ 35 | A placeholder for efficient implementations of functions that can be computed from the solution 36 | vector to a system of linear equations. 37 | 38 | .. currentmodule:: quantum_linear_solvers.linear_solvers 39 | 40 | Linear Solvers 41 | ============== 42 | 43 | .. autosummary:: 44 | :toctree: ../stubs/ 45 | :nosignatures: 46 | 47 | LinearSolver 48 | LinearSolverResult 49 | HHL 50 | NumPyLinearSolver 51 | 52 | Matrices 53 | ======== 54 | 55 | .. autosummary:: 56 | :toctree: ../stubs/ 57 | :nosignatures: 58 | 59 | LinearSystemMatrix 60 | NumPyMatrix 61 | TridiagonalToeplitz 62 | 63 | Observables 64 | =========== 65 | 66 | .. autosummary:: 67 | :toctree: ../stubs/ 68 | 69 | LinearSystemObservable 70 | AbsoluteAverage 71 | MatrixFunctional 72 | 73 | """ 74 | 75 | from .hhl import HHL 76 | from .numpy_linear_solver import NumPyLinearSolver 77 | from .linear_solver import LinearSolver, LinearSolverResult 78 | from .matrices import LinearSystemMatrix, NumPyMatrix, TridiagonalToeplitz 79 | from .observables import LinearSystemObservable, AbsoluteAverage, MatrixFunctional 80 | 81 | __all__ = [ 82 | "HHL", 83 | "NumPyLinearSolver", 84 | "LinearSolver", 85 | "LinearSolverResult", 86 | "LinearSystemMatrix", 87 | "NumPyMatrix", 88 | "TridiagonalToeplitz", 89 | "LinearSystemObservable", 90 | "AbsoluteAverage", 91 | "MatrixFunctional", 92 | ] 93 | -------------------------------------------------------------------------------- /linear_solvers/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/linear_solvers/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /linear_solvers/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/linear_solvers/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /linear_solvers/__pycache__/hhl.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/linear_solvers/__pycache__/hhl.cpython-38.pyc -------------------------------------------------------------------------------- /linear_solvers/__pycache__/hhl.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/linear_solvers/__pycache__/hhl.cpython-39.pyc -------------------------------------------------------------------------------- /linear_solvers/__pycache__/linear_solver.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/linear_solvers/__pycache__/linear_solver.cpython-38.pyc -------------------------------------------------------------------------------- /linear_solvers/__pycache__/linear_solver.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/linear_solvers/__pycache__/linear_solver.cpython-39.pyc -------------------------------------------------------------------------------- /linear_solvers/__pycache__/numpy_linear_solver.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/linear_solvers/__pycache__/numpy_linear_solver.cpython-38.pyc -------------------------------------------------------------------------------- /linear_solvers/__pycache__/numpy_linear_solver.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/linear_solvers/__pycache__/numpy_linear_solver.cpython-39.pyc -------------------------------------------------------------------------------- /linear_solvers/hhl.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021, 2022. 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 | """The HHL algorithm.""" 14 | 15 | from typing import Optional, Union, List, Callable, Tuple 16 | import numpy as np 17 | 18 | from qiskit.circuit import QuantumCircuit, QuantumRegister, AncillaRegister 19 | from qiskit.circuit.library import PhaseEstimation 20 | from qiskit.circuit.library.arithmetic.piecewise_chebyshev import PiecewiseChebyshev 21 | from qiskit.circuit.library.arithmetic.exact_reciprocal import ExactReciprocal 22 | from qiskit.opflow import ( 23 | Z, 24 | I, 25 | StateFn, 26 | TensoredOp, 27 | ExpectationBase, 28 | CircuitSampler, 29 | ListOp, 30 | ExpectationFactory, 31 | ComposedOp, 32 | ) 33 | from qiskit.providers import Backend 34 | from qiskit.utils import QuantumInstance 35 | 36 | from .linear_solver import LinearSolver, LinearSolverResult 37 | from .matrices.numpy_matrix import NumPyMatrix 38 | from .observables.linear_system_observable import LinearSystemObservable 39 | 40 | 41 | class HHL(LinearSolver): 42 | r"""Systems of linear equations arise naturally in many real-life applications in a wide range 43 | of areas, such as in the solution of Partial Differential Equations, the calibration of 44 | financial models, fluid simulation or numerical field calculation. The problem can be defined 45 | as, given a matrix :math:`A\in\mathbb{C}^{N\times N}` and a vector 46 | :math:`\vec{b}\in\mathbb{C}^{N}`, find :math:`\vec{x}\in\mathbb{C}^{N}` satisfying 47 | :math:`A\vec{x}=\vec{b}`. 48 | 49 | A system of linear equations is called :math:`s`-sparse if :math:`A` has at most :math:`s` 50 | non-zero entries per row or column. Solving an :math:`s`-sparse system of size :math:`N` with 51 | a classical computer requires :math:`\mathcal{ O }(Ns\kappa\log(1/\epsilon))` running time 52 | using the conjugate gradient method. Here :math:`\kappa` denotes the condition number of the 53 | system and :math:`\epsilon` the accuracy of the approximation. 54 | 55 | The HHL is a quantum algorithm to estimate a function of the solution with running time 56 | complexity of :math:`\mathcal{ O }(\log(N)s^{2}\kappa^{2}/\epsilon)` when 57 | :math:`A` is a Hermitian matrix under the assumptions of efficient oracles for loading the 58 | data, Hamiltonian simulation and computing a function of the solution. This is an exponential 59 | speed up in the size of the system, however one crucial remark to keep in mind is that the 60 | classical algorithm returns the full solution, while the HHL can only approximate functions of 61 | the solution vector. 62 | 63 | Examples: 64 | 65 | .. jupyter-execute:: 66 | 67 | import numpy as np 68 | from qiskit import QuantumCircuit 69 | from quantum_linear_solvers.linear_solvers.hhl import HHL 70 | from quantum_linear_solvers.linear_solvers.matrices import TridiagonalToeplitz 71 | from quantum_linear_solvers.linear_solvers.observables import MatrixFunctional 72 | 73 | matrix = TridiagonalToeplitz(2, 1, 1 / 3, trotter_steps=2) 74 | right_hand_side = [1.0, -2.1, 3.2, -4.3] 75 | observable = MatrixFunctional(1, 1 / 2) 76 | rhs = right_hand_side / np.linalg.norm(right_hand_side) 77 | 78 | # Initial state circuit 79 | num_qubits = matrix.num_state_qubits 80 | qc = QuantumCircuit(num_qubits) 81 | qc.isometry(rhs, list(range(num_qubits)), None) 82 | 83 | hhl = HHL() 84 | solution = hhl.solve(matrix, qc, observable) 85 | approx_result = solution.observable 86 | 87 | References: 88 | 89 | [1]: Harrow, A. W., Hassidim, A., Lloyd, S. (2009). 90 | Quantum algorithm for linear systems of equations. 91 | `Phys. Rev. Lett. 103, 15 (2009), 1–15. `_ 92 | 93 | [2]: Carrera Vazquez, A., Hiptmair, R., & Woerner, S. (2020). 94 | Enhancing the Quantum Linear Systems Algorithm using Richardson Extrapolation. 95 | `arXiv:2009.04484 `_ 96 | 97 | """ 98 | 99 | def __init__( 100 | self, 101 | epsilon: float = 1e-2, 102 | expectation: Optional[ExpectationBase] = None, 103 | quantum_instance: Optional[Union[Backend, QuantumInstance]] = None, 104 | ) -> None: 105 | r""" 106 | Args: 107 | epsilon: Error tolerance of the approximation to the solution, i.e. if :math:`x` is the 108 | exact solution and :math:`\tilde{x}` the one calculated by the algorithm, then 109 | :math:`||x - \tilde{x}|| \le epsilon`. 110 | expectation: The expectation converter applied to the expectation values before 111 | evaluation. If None then PauliExpectation is used. 112 | quantum_instance: Quantum Instance or Backend. If None, a Statevector calculation is 113 | done. 114 | """ 115 | super().__init__() 116 | 117 | self._epsilon = epsilon 118 | # Tolerance for the different parts of the algorithm as per [1] 119 | self._epsilon_r = epsilon / 3 # conditioned rotation 120 | self._epsilon_s = epsilon / 3 # state preparation 121 | self._epsilon_a = epsilon / 6 # hamiltonian simulation 122 | 123 | self._scaling = None # scaling of the solution 124 | 125 | self._sampler = None 126 | self.quantum_instance = quantum_instance 127 | 128 | self._expectation = expectation 129 | 130 | # For now the default reciprocal implementation is exact 131 | self._exact_reciprocal = True 132 | # Set the default scaling to 1 133 | self.scaling = 1 134 | 135 | @property 136 | def quantum_instance(self) -> Optional[QuantumInstance]: 137 | """Get the quantum instance. 138 | 139 | Returns: 140 | The quantum instance used to run this algorithm. 141 | """ 142 | return None if self._sampler is None else self._sampler.quantum_instance 143 | 144 | @quantum_instance.setter 145 | def quantum_instance( 146 | self, quantum_instance: Optional[Union[QuantumInstance, Backend]] 147 | ) -> None: 148 | """Set quantum instance. 149 | 150 | Args: 151 | quantum_instance: The quantum instance used to run this algorithm. 152 | If None, a Statevector calculation is done. 153 | """ 154 | if quantum_instance is not None: 155 | self._sampler = CircuitSampler(quantum_instance) 156 | else: 157 | self._sampler = None 158 | 159 | @property 160 | def scaling(self) -> float: 161 | """The scaling of the solution vector.""" 162 | return self._scaling 163 | 164 | @scaling.setter 165 | def scaling(self, scaling: float) -> None: 166 | """Set the new scaling of the solution vector.""" 167 | self._scaling = scaling 168 | 169 | @property 170 | def expectation(self) -> ExpectationBase: 171 | """The expectation value algorithm used to construct the expectation measurement from 172 | the observable.""" 173 | return self._expectation 174 | 175 | @expectation.setter 176 | def expectation(self, expectation: ExpectationBase) -> None: 177 | """Set the expectation value algorithm.""" 178 | self._expectation = expectation 179 | 180 | def _get_delta(self, n_l: int, lambda_min: float, lambda_max: float) -> float: 181 | """Calculates the scaling factor to represent exactly lambda_min on nl binary digits. 182 | 183 | Args: 184 | n_l: The number of qubits to represent the eigenvalues. 185 | lambda_min: the smallest eigenvalue. 186 | lambda_max: the largest eigenvalue. 187 | 188 | Returns: 189 | The value of the scaling factor. 190 | """ 191 | formatstr = "#0" + str(n_l + 2) + "b" 192 | lambda_min_tilde = np.abs(lambda_min * (2**n_l - 1) / lambda_max) 193 | # floating point precision can cause problems 194 | if np.abs(lambda_min_tilde - 1) < 1e-7: 195 | lambda_min_tilde = 1 196 | binstr = format(int(lambda_min_tilde), formatstr)[2::] 197 | lamb_min_rep = 0 198 | for i, char in enumerate(binstr): 199 | lamb_min_rep += int(char) / (2 ** (i + 1)) 200 | return lamb_min_rep 201 | 202 | def _calculate_norm(self, qc: QuantumCircuit) -> float: 203 | """Calculates the value of the euclidean norm of the solution. 204 | 205 | Args: 206 | qc: The quantum circuit preparing the solution x to the system. 207 | 208 | Returns: 209 | The value of the euclidean norm of the solution. 210 | """ 211 | # Calculate the number of qubits 212 | nb = qc.qregs[0].size 213 | nl = qc.qregs[1].size 214 | na = qc.num_ancillas 215 | 216 | # Create the Operators Zero and One 217 | zero_op = (I + Z) / 2 218 | one_op = (I - Z) / 2 219 | 220 | # Norm observable 221 | observable = one_op ^ TensoredOp((nl + na) * [zero_op]) ^ (I ^ nb) 222 | norm_2 = (~StateFn(observable) @ StateFn(qc)).eval() 223 | 224 | return np.real(np.sqrt(norm_2) / self.scaling) 225 | 226 | def _calculate_observable( 227 | self, 228 | solution: QuantumCircuit, 229 | ls_observable: Optional[LinearSystemObservable] = None, 230 | observable_circuit: Optional[QuantumCircuit] = None, 231 | post_processing: Optional[ 232 | Callable[[Union[float, List[float]], int, float], float] 233 | ] = None, 234 | ) -> Tuple[float, Union[complex, List[complex]]]: 235 | """Calculates the value of the observable(s) given. 236 | 237 | Args: 238 | solution: The quantum circuit preparing the solution x to the system. 239 | ls_observable: Information to be extracted from the solution. 240 | observable_circuit: Circuit to be applied to the solution to extract information. 241 | post_processing: Function to compute the value of the observable. 242 | 243 | Returns: 244 | The value of the observable(s) and the circuit results before post-processing as a 245 | tuple. 246 | """ 247 | # Get the number of qubits 248 | nb = solution.qregs[0].size 249 | nl = solution.qregs[1].size 250 | na = solution.num_ancillas 251 | 252 | # if the observable is given construct post_processing and observable_circuit 253 | if ls_observable is not None: 254 | observable_circuit = ls_observable.observable_circuit(nb) 255 | post_processing = ls_observable.post_processing 256 | 257 | if isinstance(ls_observable, LinearSystemObservable): 258 | observable = ls_observable.observable(nb) 259 | 260 | # in the other case use the identity as observable 261 | else: 262 | observable = I ^ nb 263 | 264 | # Create the Operators Zero and One 265 | zero_op = (I + Z) / 2 266 | one_op = (I - Z) / 2 267 | 268 | is_list = True 269 | if not isinstance(observable_circuit, list): 270 | is_list = False 271 | observable_circuit = [observable_circuit] 272 | observable = [observable] 273 | 274 | expectations: Union[ListOp, ComposedOp] = [] 275 | for circ, obs in zip(observable_circuit, observable): 276 | circuit = QuantumCircuit(solution.num_qubits) 277 | circuit.append(solution, circuit.qubits) 278 | circuit.append(circ, range(nb)) 279 | 280 | ob = one_op ^ TensoredOp((nl + na) * [zero_op]) ^ obs 281 | expectations.append(~StateFn(ob) @ StateFn(circuit)) 282 | 283 | if is_list: 284 | # execute all in a list op to send circuits in batches 285 | expectations = ListOp(expectations) 286 | else: 287 | expectations = expectations[0] 288 | 289 | # check if an expectation converter is given 290 | if self._expectation is not None: 291 | expectations = self._expectation.convert(expectations) 292 | # if otherwise a backend was specified, try to set the best expectation value 293 | elif self._sampler is not None: 294 | if is_list: 295 | op = expectations.oplist[0] 296 | else: 297 | op = expectations 298 | self._expectation = ExpectationFactory.build( 299 | op, self._sampler.quantum_instance 300 | ) 301 | 302 | if self._sampler is not None: 303 | expectations = self._sampler.convert(expectations) 304 | 305 | # evaluate 306 | expectation_results = expectations.eval() 307 | 308 | # apply post_processing 309 | result = post_processing(expectation_results, nb, self.scaling) 310 | 311 | return result, expectation_results 312 | 313 | def construct_circuit( 314 | self, 315 | matrix: Union[List, np.ndarray, QuantumCircuit], 316 | vector: Union[List, np.ndarray, QuantumCircuit], 317 | neg_vals: Optional[bool] = True, 318 | ) -> QuantumCircuit: 319 | """Construct the HHL circuit. 320 | 321 | Args: 322 | matrix: The matrix specifying the system, i.e. A in Ax=b. 323 | vector: The vector specifying the right hand side of the equation in Ax=b. 324 | neg_vals: States whether the matrix has negative eigenvalues. If False the 325 | computation becomes cheaper. 326 | 327 | Returns: 328 | The HHL circuit. 329 | 330 | Raises: 331 | ValueError: If the input is not in the correct format. 332 | ValueError: If the type of the input matrix is not supported. 333 | """ 334 | # State preparation circuit - default is qiskit 335 | if isinstance(vector, QuantumCircuit): 336 | nb = vector.num_qubits 337 | vector_circuit = vector 338 | elif isinstance(vector, (list, np.ndarray)): 339 | if isinstance(vector, list): 340 | vector = np.array(vector) 341 | nb = int(np.log2(len(vector))) 342 | vector_circuit = QuantumCircuit(nb) 343 | # pylint: disable=no-member 344 | vector_circuit.isometry( 345 | vector / np.linalg.norm(vector), list(range(nb)), None 346 | ) 347 | 348 | # If state preparation is probabilistic the number of qubit flags should increase 349 | nf = 1 350 | 351 | # Hamiltonian simulation circuit - default is Trotterization 352 | if isinstance(matrix, QuantumCircuit): 353 | matrix_circuit = matrix 354 | elif isinstance(matrix, (list, np.ndarray)): 355 | if isinstance(matrix, list): 356 | matrix = np.array(matrix) 357 | 358 | if matrix.shape[0] != matrix.shape[1]: 359 | raise ValueError("Input matrix must be square!") 360 | if np.log2(matrix.shape[0]) % 1 != 0: 361 | raise ValueError("Input matrix dimension must be 2^n!") 362 | if not np.allclose(matrix, matrix.conj().T): 363 | raise ValueError("Input matrix must be hermitian!") 364 | if matrix.shape[0] != 2**vector_circuit.num_qubits: 365 | raise ValueError( 366 | "Input vector dimension does not match input " 367 | "matrix dimension! Vector dimension: " 368 | + str(vector_circuit.num_qubits) 369 | + ". Matrix dimension: " 370 | + str(matrix.shape[0]) 371 | ) 372 | matrix_circuit = NumPyMatrix(matrix, evolution_time=2 * np.pi) 373 | else: 374 | raise ValueError(f"Invalid type for matrix: {type(matrix)}.") 375 | 376 | # Set the tolerance for the matrix approximation 377 | if hasattr(matrix_circuit, "tolerance"): 378 | matrix_circuit.tolerance = self._epsilon_a 379 | 380 | # check if the matrix can calculate the condition number and store the upper bound 381 | if ( 382 | hasattr(matrix_circuit, "condition_bounds") 383 | and matrix_circuit.condition_bounds() is not None 384 | ): 385 | kappa = matrix_circuit.condition_bounds()[1] 386 | else: 387 | kappa = 1 388 | # Update the number of qubits required to represent the eigenvalues 389 | # The +neg_vals is to register negative eigenvalues because 390 | # e^{-2 \pi i \lambda} = e^{2 \pi i (1 - \lambda)} 391 | nl = max(nb + 1, int(np.ceil(np.log2(kappa + 1)))) + neg_vals 392 | 393 | # check if the matrix can calculate bounds for the eigenvalues 394 | if ( 395 | hasattr(matrix_circuit, "eigs_bounds") 396 | and matrix_circuit.eigs_bounds() is not None 397 | ): 398 | lambda_min, lambda_max = matrix_circuit.eigs_bounds() 399 | # Constant so that the minimum eigenvalue is represented exactly, since it contributes 400 | # the most to the solution of the system. -1 to take into account the sign qubit 401 | delta = self._get_delta(nl - neg_vals, lambda_min, lambda_max) 402 | # Update evolution time 403 | matrix_circuit.evolution_time = ( 404 | 2 * np.pi * delta / lambda_min / (2**neg_vals) 405 | ) 406 | # Update the scaling of the solution 407 | self.scaling = lambda_min 408 | else: 409 | delta = 1 / (2**nl) 410 | print("The solution will be calculated up to a scaling factor.") 411 | 412 | if self._exact_reciprocal: 413 | reciprocal_circuit = ExactReciprocal(nl, delta, neg_vals=neg_vals) 414 | # Update number of ancilla qubits 415 | na = matrix_circuit.num_ancillas 416 | else: 417 | # Calculate breakpoints for the reciprocal approximation 418 | num_values = 2**nl 419 | constant = delta 420 | a = int(round(num_values ** (2 / 3))) 421 | 422 | # Calculate the degree of the polynomial and the number of intervals 423 | r = 2 * constant / a + np.sqrt(np.abs(1 - (2 * constant / a) ** 2)) 424 | degree = min( 425 | nb, 426 | int( 427 | np.log( 428 | 1 429 | + ( 430 | 16.23 431 | * np.sqrt(np.log(r) ** 2 + (np.pi / 2) ** 2) 432 | * kappa 433 | * (2 * kappa - self._epsilon_r) 434 | ) 435 | / self._epsilon_r 436 | ) 437 | ), 438 | ) 439 | num_intervals = int(np.ceil(np.log((num_values - 1) / a) / np.log(5))) 440 | 441 | # Calculate breakpoints and polynomials 442 | breakpoints = [] 443 | for i in range(0, num_intervals): 444 | # Add the breakpoint to the list 445 | breakpoints.append(a * (5**i)) 446 | 447 | # Define the right breakpoint of the interval 448 | if i == num_intervals - 1: 449 | breakpoints.append(num_values - 1) 450 | 451 | reciprocal_circuit = PiecewiseChebyshev( 452 | lambda x: np.arcsin(constant / x), degree, breakpoints, nl 453 | ) 454 | na = max(matrix_circuit.num_ancillas, reciprocal_circuit.num_ancillas) 455 | 456 | # Initialise the quantum registers 457 | qb = QuantumRegister(nb) # right hand side and solution 458 | ql = QuantumRegister(nl) # eigenvalue evaluation qubits 459 | if na > 0: 460 | qa = AncillaRegister(na) # ancilla qubits 461 | qf = QuantumRegister(nf) # flag qubits 462 | 463 | if na > 0: 464 | qc = QuantumCircuit(qb, ql, qa, qf) 465 | else: 466 | qc = QuantumCircuit(qb, ql, qf) 467 | 468 | # State preparation 469 | qc.append(vector_circuit, qb[:]) 470 | # QPE 471 | phase_estimation = PhaseEstimation(nl, matrix_circuit) 472 | if na > 0: 473 | qc.append( 474 | phase_estimation, ql[:] + qb[:] + qa[: matrix_circuit.num_ancillas] 475 | ) 476 | else: 477 | qc.append(phase_estimation, ql[:] + qb[:]) 478 | # Conditioned rotation 479 | if self._exact_reciprocal: 480 | qc.append(reciprocal_circuit, ql[::-1] + [qf[0]]) 481 | else: 482 | qc.append( 483 | reciprocal_circuit.to_instruction(), 484 | ql[:] + [qf[0]] + qa[: reciprocal_circuit.num_ancillas], 485 | ) 486 | # QPE inverse 487 | if na > 0: 488 | qc.append( 489 | phase_estimation.inverse(), 490 | ql[:] + qb[:] + qa[: matrix_circuit.num_ancillas], 491 | ) 492 | else: 493 | qc.append(phase_estimation.inverse(), ql[:] + qb[:]) 494 | return qc 495 | 496 | def solve( 497 | self, 498 | matrix: Union[List, np.ndarray, QuantumCircuit], 499 | vector: Union[List, np.ndarray, QuantumCircuit], 500 | observable: Optional[ 501 | Union[ 502 | LinearSystemObservable, 503 | List[LinearSystemObservable], 504 | ] 505 | ] = None, 506 | observable_circuit: Optional[ 507 | Union[QuantumCircuit, List[QuantumCircuit]] 508 | ] = None, 509 | post_processing: Optional[ 510 | Callable[[Union[float, List[float]], int, float], float] 511 | ] = None, 512 | ) -> LinearSolverResult: 513 | """Tries to solve the given linear system of equations. 514 | 515 | Args: 516 | matrix: The matrix specifying the system, i.e. A in Ax=b. 517 | vector: The vector specifying the right hand side of the equation in Ax=b. 518 | observable: Optional information to be extracted from the solution. 519 | Default is the probability of success of the algorithm. 520 | observable_circuit: Optional circuit to be applied to the solution to extract 521 | information. Default is `None`. 522 | post_processing: Optional function to compute the value of the observable. 523 | Default is the raw value of measuring the observable. 524 | 525 | Raises: 526 | ValueError: If an invalid combination of observable, observable_circuit and 527 | post_processing is passed. 528 | 529 | Returns: 530 | The result object containing information about the solution vector of the linear 531 | system. 532 | """ 533 | # verify input 534 | if observable is not None: 535 | if observable_circuit is not None or post_processing is not None: 536 | raise ValueError( 537 | "If observable is passed, observable_circuit and post_processing cannot be set." 538 | ) 539 | 540 | solution = LinearSolverResult() 541 | solution.state = self.construct_circuit(matrix, vector) 542 | solution.euclidean_norm = self._calculate_norm(solution.state) 543 | 544 | if isinstance(observable, List): 545 | observable_all, circuit_results_all = [], [] 546 | for obs in observable: 547 | obs_i, circ_results_i = self._calculate_observable( 548 | solution.state, obs, observable_circuit, post_processing 549 | ) 550 | observable_all.append(obs_i) 551 | circuit_results_all.append(circ_results_i) 552 | solution.observable = observable_all 553 | solution.circuit_results = circuit_results_all 554 | elif observable is not None or observable_circuit is not None: 555 | solution.observable, solution.circuit_results = self._calculate_observable( 556 | solution.state, observable, observable_circuit, post_processing 557 | ) 558 | 559 | return solution 560 | -------------------------------------------------------------------------------- /linear_solvers/linear_solver.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021, 2022. 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 | """An abstract class for linear systems solvers.""" 14 | 15 | from abc import ABC, abstractmethod 16 | from typing import Union, Optional, List, Callable 17 | import numpy as np 18 | 19 | from qiskit import QuantumCircuit 20 | from qiskit.algorithms.algorithm_result import AlgorithmResult 21 | 22 | from .observables.linear_system_observable import LinearSystemObservable 23 | 24 | # pylint: disable=too-few-public-methods 25 | 26 | 27 | class LinearSolverResult(AlgorithmResult): 28 | """A base class for linear systems results. 29 | 30 | The linear systems algorithms return an object of the type ``LinearSystemsResult`` 31 | with the information about the solution obtained. 32 | """ 33 | 34 | def __init__(self) -> None: 35 | super().__init__() 36 | 37 | # Set the default to None, if the algorithm knows how to calculate it can override it. 38 | self._state: Optional[Union[QuantumCircuit, np.ndarray]] = None 39 | self._observable: Optional[Union[float, List[float]]] = None 40 | self._euclidean_norm: Optional[float] = None 41 | self._circuit_results: Optional[ 42 | Union[complex, List[complex], List[Union[complex, List[complex]]]] 43 | ] = None 44 | 45 | @property 46 | def observable(self) -> Union[float, List[float]]: 47 | """return the (list of) calculated observable(s)""" 48 | return self._observable 49 | 50 | @observable.setter 51 | def observable(self, observable: Union[float, List[float]]) -> None: 52 | """Set the value(s) of the observable(s). 53 | 54 | Args: 55 | observable: The new value(s) of the observable(s). 56 | """ 57 | self._observable = observable 58 | 59 | @property 60 | def state(self) -> Union[QuantumCircuit, np.ndarray]: 61 | """return either the circuit that prepares the solution or the solution as a vector""" 62 | return self._state 63 | 64 | @state.setter 65 | def state(self, state: Union[QuantumCircuit, np.ndarray]) -> None: 66 | """Set the solution state as either the circuit that prepares it or as a vector. 67 | 68 | Args: 69 | state: The new solution state. 70 | """ 71 | self._state = state 72 | 73 | @property 74 | def euclidean_norm(self) -> float: 75 | """return the euclidean norm if the algorithm knows how to calculate it""" 76 | return self._euclidean_norm 77 | 78 | @euclidean_norm.setter 79 | def euclidean_norm(self, norm: float) -> None: 80 | """Set the euclidean norm of the solution. 81 | 82 | Args: 83 | norm: The new euclidean norm of the solution. 84 | """ 85 | self._euclidean_norm = norm 86 | 87 | @property 88 | def circuit_results( 89 | self, 90 | ) -> Union[complex, List[complex], List[Union[complex, List[complex]]]]: 91 | """return the results from the circuits""" 92 | return self._circuit_results 93 | 94 | @circuit_results.setter 95 | def circuit_results( 96 | self, 97 | results: Union[complex, List[complex], List[Union[complex, List[complex]]]], 98 | ): 99 | self._circuit_results = results 100 | 101 | 102 | class LinearSolver(ABC): 103 | """An abstract class for linear system solvers in Qiskit.""" 104 | 105 | @abstractmethod 106 | def solve( 107 | self, 108 | matrix: Union[np.ndarray, QuantumCircuit], 109 | vector: Union[np.ndarray, QuantumCircuit], 110 | observable: Optional[ 111 | Union[ 112 | LinearSystemObservable, 113 | List[LinearSystemObservable], 114 | ] 115 | ] = None, 116 | observable_circuit: Optional[ 117 | Union[QuantumCircuit, List[QuantumCircuit]] 118 | ] = None, 119 | post_processing: Optional[ 120 | Callable[[Union[float, List[float]], int, float], float] 121 | ] = None, 122 | ) -> LinearSolverResult: 123 | """Solve the system and compute the observable(s) 124 | 125 | Args: 126 | matrix: The matrix specifying the system, i.e. A in Ax=b. 127 | vector: The vector specifying the right hand side of the equation in Ax=b. 128 | observable: Optional information to be extracted from the solution. 129 | Default is the probability of success of the algorithm. 130 | observable_circuit: Optional circuit to be applied to the solution to extract 131 | information. Default is ``None``. 132 | post_processing: Optional function to compute the value of the observable. 133 | Default is the raw value of measuring the observable. 134 | 135 | Returns: 136 | The result of the linear system. 137 | """ 138 | raise NotImplementedError 139 | -------------------------------------------------------------------------------- /linear_solvers/matrices/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021, 2022. 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 | """System matrices for Qiskit's linear solvers.""" 14 | 15 | from .linear_system_matrix import LinearSystemMatrix 16 | from .numpy_matrix import NumPyMatrix 17 | from .tridiagonal_toeplitz import TridiagonalToeplitz 18 | 19 | __all__ = ["LinearSystemMatrix", "NumPyMatrix", "TridiagonalToeplitz"] 20 | -------------------------------------------------------------------------------- /linear_solvers/matrices/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/linear_solvers/matrices/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /linear_solvers/matrices/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/linear_solvers/matrices/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /linear_solvers/matrices/__pycache__/linear_system_matrix.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/linear_solvers/matrices/__pycache__/linear_system_matrix.cpython-38.pyc -------------------------------------------------------------------------------- /linear_solvers/matrices/__pycache__/linear_system_matrix.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/linear_solvers/matrices/__pycache__/linear_system_matrix.cpython-39.pyc -------------------------------------------------------------------------------- /linear_solvers/matrices/__pycache__/numpy_matrix.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/linear_solvers/matrices/__pycache__/numpy_matrix.cpython-38.pyc -------------------------------------------------------------------------------- /linear_solvers/matrices/__pycache__/numpy_matrix.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/linear_solvers/matrices/__pycache__/numpy_matrix.cpython-39.pyc -------------------------------------------------------------------------------- /linear_solvers/matrices/__pycache__/tridiagonal_toeplitz.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/linear_solvers/matrices/__pycache__/tridiagonal_toeplitz.cpython-38.pyc -------------------------------------------------------------------------------- /linear_solvers/matrices/__pycache__/tridiagonal_toeplitz.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/linear_solvers/matrices/__pycache__/tridiagonal_toeplitz.cpython-39.pyc -------------------------------------------------------------------------------- /linear_solvers/matrices/linear_system_matrix.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021, 2022. 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 | """An abstract class for matrices input to the linear systems solvers in Qiskit.""" 14 | 15 | from abc import ABC, abstractmethod 16 | from typing import Tuple 17 | 18 | from qiskit import QuantumCircuit 19 | from qiskit.circuit.library import BlueprintCircuit 20 | 21 | 22 | class LinearSystemMatrix(BlueprintCircuit, ABC): 23 | """Base class for linear system matrices.""" 24 | 25 | def __init__( 26 | self, 27 | num_state_qubits: int, 28 | tolerance: float, 29 | evolution_time: float, 30 | name: str = "ls_matrix", 31 | ) -> None: 32 | """ 33 | Args: 34 | num_state_qubits: the number of qubits where the unitary acts. 35 | tolerance: the accuracy desired for the approximation 36 | evolution_time: the time of the Hamiltonian simulation 37 | name: The name of the object. 38 | """ 39 | super().__init__(name=name) 40 | 41 | # store parameters 42 | self._num_state_qubits = num_state_qubits 43 | self._reset_registers(num_state_qubits) 44 | self.tolerance = tolerance 45 | self.evolution_time = evolution_time 46 | 47 | @property 48 | def num_state_qubits(self) -> int: 49 | r"""The number of state qubits representing the state :math:`|x\rangle`. 50 | 51 | Returns: 52 | The number of state qubits. 53 | """ 54 | return self._num_state_qubits 55 | 56 | @num_state_qubits.setter 57 | def num_state_qubits(self, num_state_qubits: int) -> None: 58 | """Set the number of state qubits. 59 | 60 | Note that this may change the underlying quantum register, if the number of state qubits 61 | changes. 62 | 63 | Args: 64 | num_state_qubits: The new number of qubits. 65 | """ 66 | if num_state_qubits != self._num_state_qubits: 67 | self._invalidate() 68 | self._num_state_qubits = num_state_qubits 69 | self._reset_registers(num_state_qubits) 70 | 71 | @property 72 | def tolerance(self) -> float: 73 | """Return the error tolerance""" 74 | return self._tolerance 75 | 76 | @tolerance.setter 77 | def tolerance(self, tolerance: float) -> None: 78 | """Set the error tolerance 79 | Args: 80 | tolerance: The new error tolerance. 81 | """ 82 | self._tolerance = tolerance 83 | 84 | @property 85 | def evolution_time(self) -> float: 86 | """Return the time of the evolution.""" 87 | return self._evolution_time 88 | 89 | @evolution_time.setter 90 | def evolution_time(self, evolution_time: float) -> None: 91 | """Set the time of the evolution. 92 | 93 | Args: 94 | evolution_time: The new time of the evolution. 95 | """ 96 | self._evolution_time = evolution_time 97 | 98 | @abstractmethod 99 | def eigs_bounds(self) -> Tuple[float, float]: 100 | """Return lower and upper bounds on the eigenvalues of the matrix.""" 101 | raise NotImplementedError 102 | 103 | @abstractmethod 104 | def condition_bounds(self) -> Tuple[float, float]: 105 | """Return lower and upper bounds on the condition number of the matrix.""" 106 | raise NotImplementedError 107 | 108 | @abstractmethod 109 | def _reset_registers(self, num_state_qubits: int) -> None: 110 | """Reset the registers according to the new number of state qubits. 111 | 112 | Args: 113 | num_state_qubits: The new number of qubits. 114 | """ 115 | raise NotImplementedError 116 | 117 | @abstractmethod 118 | def power(self, power: int, matrix_power: bool = False) -> QuantumCircuit: 119 | """Build powers of the circuit. 120 | 121 | Args: 122 | power: The power to raise this circuit to. 123 | matrix_power: If True, the circuit is converted to a matrix and then the 124 | matrix power is computed. If False, and ``power`` is a positive integer, 125 | the implementation defaults to ``repeat``. 126 | 127 | Returns: 128 | The quantum circuit implementing powers of the unitary. 129 | """ 130 | raise NotImplementedError 131 | -------------------------------------------------------------------------------- /linear_solvers/matrices/numpy_matrix.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021, 2022. 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 | """Hamiltonian simulation of matrices given as numpy arrays.""" 14 | 15 | from typing import Tuple 16 | import numpy as np 17 | import scipy as sp 18 | 19 | from qiskit import QuantumCircuit, QuantumRegister 20 | 21 | from .linear_system_matrix import LinearSystemMatrix 22 | 23 | 24 | class NumPyMatrix(LinearSystemMatrix): 25 | """Class of matrices given as a numpy array. 26 | 27 | Examples: 28 | 29 | .. jupyter-execute:: 30 | 31 | import numpy as np 32 | from qiskit import QuantumCircuit 33 | from quantum_linear_solvers.linear_solvers.matrices.numpy_matrix import NumPyMatrix 34 | 35 | matrix = NumPyMatrix(np.array([[1 / 2, 1 / 6, 0, 0], [1 / 6, 1 / 2, 1 / 6, 0], 36 | [0, 1 / 6, 1 / 2, 1 / 6], [0, 0, 1 / 6, 1 / 2]])) 37 | power = 2 38 | 39 | num_qubits = matrix.num_state_qubits 40 | # Controlled power (as used within QPE) 41 | pow_circ = matrix.power(power).control() 42 | circ_qubits = pow_circ.num_qubits 43 | qc = QuantumCircuit(circ_qubits) 44 | qc.append(matrix.power(power).control(), list(range(circ_qubits))) 45 | """ 46 | 47 | def __init__( 48 | self, 49 | matrix: np.ndarray, 50 | tolerance: float = 1e-2, 51 | evolution_time: float = 1.0, 52 | name: str = "np_matrix", 53 | ) -> None: 54 | """ 55 | Args: 56 | matrix: The matrix defining the linear system problem. 57 | tolerance: The accuracy desired for the approximation. 58 | evolution_time: The time of the Hamiltonian simulation. 59 | name: The name of the object. 60 | """ 61 | 62 | # define internal parameters 63 | self._num_state_qubits = None 64 | self._tolerance = None 65 | self._evolution_time = None # makes sure the eigenvalues are contained in [0,1) 66 | self._matrix = None 67 | 68 | super().__init__( 69 | num_state_qubits=int(np.log2(matrix.shape[0])), 70 | tolerance=tolerance, 71 | evolution_time=evolution_time, 72 | name=name, 73 | ) 74 | 75 | # store parameters 76 | self.num_state_qubits = int(np.log2(matrix.shape[0])) 77 | self.tolerance = tolerance 78 | self.evolution_time = evolution_time 79 | self.matrix = matrix 80 | 81 | @property 82 | def num_state_qubits(self) -> int: 83 | r"""The number of state qubits representing the state :math:`|x\rangle`. 84 | 85 | Returns: 86 | The number of state qubits. 87 | """ 88 | return self._num_state_qubits 89 | 90 | @num_state_qubits.setter 91 | def num_state_qubits(self, num_state_qubits: int) -> None: 92 | """Set the number of state qubits. 93 | 94 | Note that this may change the underlying quantum register, if the number of state qubits 95 | changes. 96 | 97 | Args: 98 | num_state_qubits: The new number of qubits. 99 | """ 100 | if num_state_qubits != self._num_state_qubits: 101 | self._invalidate() 102 | self._num_state_qubits = num_state_qubits 103 | self._reset_registers(num_state_qubits) 104 | 105 | @property 106 | def tolerance(self) -> float: 107 | """Return the error tolerance""" 108 | return self._tolerance 109 | 110 | @tolerance.setter 111 | def tolerance(self, tolerance: float) -> None: 112 | """Set the error tolerance 113 | Args: 114 | tolerance: The new error tolerance. 115 | """ 116 | self._tolerance = tolerance 117 | 118 | @property 119 | def evolution_time(self) -> float: 120 | """Return the time of the evolution.""" 121 | return self._evolution_time 122 | 123 | @evolution_time.setter 124 | def evolution_time(self, evolution_time: float) -> None: 125 | """Set the time of the evolution. 126 | 127 | Args: 128 | evolution_time: The new time of the evolution. 129 | """ 130 | self._evolution_time = evolution_time 131 | 132 | @property 133 | def matrix(self) -> np.ndarray: 134 | """Return the matrix.""" 135 | return self._matrix 136 | 137 | @matrix.setter 138 | def matrix(self, matrix: np.ndarray) -> None: 139 | """Set the matrix. 140 | 141 | Args: 142 | matrix: The new matrix. 143 | """ 144 | self._matrix = matrix 145 | 146 | def eigs_bounds(self) -> Tuple[float, float]: 147 | """Return lower and upper bounds on the eigenvalues of the matrix.""" 148 | matrix_array = self.matrix 149 | lambda_max = max(np.abs(np.linalg.eigvals(matrix_array))) 150 | lambda_min = min(np.abs(np.linalg.eigvals(matrix_array))) 151 | return lambda_min, lambda_max 152 | 153 | def condition_bounds(self) -> Tuple[float, float]: 154 | """Return lower and upper bounds on the condition number of the matrix.""" 155 | matrix_array = self.matrix 156 | kappa = np.linalg.cond(matrix_array) 157 | return kappa, kappa 158 | 159 | def _check_configuration(self, raise_on_failure: bool = True) -> bool: 160 | """Check if the current configuration is valid.""" 161 | valid = True 162 | 163 | if self.matrix.shape[0] != self.matrix.shape[1]: 164 | if raise_on_failure: 165 | raise AttributeError("Input matrix must be square!") 166 | return False 167 | if np.log2(self.matrix.shape[0]) % 1 != 0: 168 | if raise_on_failure: 169 | raise AttributeError("Input matrix dimension must be 2^n!") 170 | return False 171 | if not np.allclose(self.matrix, self.matrix.conj().T): 172 | if raise_on_failure: 173 | raise AttributeError("Input matrix must be hermitian!") 174 | return False 175 | 176 | return valid 177 | 178 | def _reset_registers(self, num_state_qubits: int) -> None: 179 | """Reset the quantum registers. 180 | 181 | Args: 182 | num_state_qubits: The number of qubits to represent the matrix. 183 | """ 184 | qr_state = QuantumRegister(num_state_qubits, "state") 185 | self.qregs = [qr_state] 186 | 187 | def _build(self) -> None: 188 | """If not already built, build the circuit.""" 189 | if self._is_built: 190 | return 191 | 192 | super()._build() 193 | 194 | self.compose(self.power(1), inplace=True) 195 | 196 | def inverse(self): 197 | return NumPyMatrix(self.matrix, evolution_time=-1 * self.evolution_time) 198 | 199 | def power(self, power: int, matrix_power: bool = False) -> QuantumCircuit: 200 | """Build powers of the circuit. 201 | 202 | Args: 203 | power: The power to raise this circuit to. 204 | matrix_power: If True, the circuit is converted to a matrix and then the 205 | matrix power is computed. If False, and ``power`` is a positive integer, 206 | the implementation defaults to ``repeat``. 207 | 208 | Returns: 209 | The quantum circuit implementing powers of the unitary. 210 | """ 211 | qc = QuantumCircuit(self.num_state_qubits) 212 | evolved = sp.linalg.expm(1j * self.matrix * self.evolution_time) 213 | # pylint: disable=no-member 214 | qc.unitary(evolved, qc.qubits) 215 | return qc.power(power) 216 | -------------------------------------------------------------------------------- /linear_solvers/matrices/tridiagonal_toeplitz.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021, 2022. 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 | """Hamiltonian simulation of tridiagonal Toeplitz symmetric matrices.""" 14 | 15 | from typing import Tuple, List 16 | import numpy as np 17 | from scipy.sparse import diags 18 | 19 | from qiskit.circuit import QuantumCircuit, QuantumRegister, AncillaRegister 20 | from qiskit.circuit.library import UGate, MCMTVChain 21 | 22 | from .linear_system_matrix import LinearSystemMatrix 23 | 24 | 25 | class TridiagonalToeplitz(LinearSystemMatrix): 26 | r"""Class of tridiagonal Toeplitz symmetric matrices. 27 | 28 | Given the main entry, :math:`a`, and the off diagonal entry, :math:`b`, the :math:`4\times 4` 29 | dimensional tridiagonal Toeplitz symmetric matrix is 30 | 31 | .. math:: 32 | 33 | \begin{pmatrix} 34 | a & b & 0 & 0 \\ 35 | b & a & b & 0 \\ 36 | 0 & b & a & b \\ 37 | 0 & 0 & b & a 38 | \end{pmatrix}. 39 | 40 | Examples: 41 | 42 | .. jupyter-execute:: 43 | 44 | import numpy as np 45 | from qiskit import QuantumCircuit 46 | from quantum_linear_solvers.linear_solvers.matrices import TridiagonalToeplitz 47 | 48 | matrix = TridiagonalToeplitz(2, 1, -1 / 3) 49 | power = 3 50 | 51 | # Controlled power (as within QPE) 52 | num_qubits = matrix.num_state_qubits 53 | pow_circ = matrix.power(power).control() 54 | circ_qubits = pow_circ.num_qubits 55 | qc = QuantumCircuit(circ_qubits) 56 | qc.append(matrix.power(power).control(), list(range(circ_qubits))) 57 | """ 58 | 59 | def __init__( 60 | self, 61 | num_state_qubits: int, 62 | main_diag: float, 63 | off_diag: float, 64 | tolerance: float = 1e-2, 65 | evolution_time: float = 1.0, 66 | trotter_steps: int = 1, 67 | name: str = "tridi", 68 | ) -> None: 69 | """ 70 | Args: 71 | num_state_qubits: the number of qubits where the unitary acts. 72 | main_diag: the main diagonal entry 73 | off_diag: the off diagonal entry 74 | tolerance: the accuracy desired for the approximation 75 | evolution_time: the time of the Hamiltonian simulation 76 | trotter_steps: the number of Trotter steps 77 | name: The name of the object. 78 | """ 79 | # define internal parameters 80 | self._main_diag = None 81 | self._off_diag = None 82 | self._tolerance = None 83 | self._evolution_time = None # makes sure the eigenvalues are contained in [0,1) 84 | self._trotter_steps = None 85 | 86 | # store parameters 87 | self.main_diag = main_diag 88 | self.off_diag = off_diag 89 | super().__init__( 90 | num_state_qubits=num_state_qubits, 91 | tolerance=tolerance, 92 | evolution_time=evolution_time, 93 | name=name, 94 | ) 95 | self.trotter_steps = trotter_steps 96 | 97 | @property 98 | def num_state_qubits(self) -> int: 99 | r"""The number of state qubits representing the state :math:`|x\rangle`. 100 | 101 | Returns: 102 | The number of state qubits. 103 | """ 104 | return self._num_state_qubits 105 | 106 | @num_state_qubits.setter 107 | def num_state_qubits(self, num_state_qubits: int) -> None: 108 | """Set the number of state qubits. 109 | 110 | Note that this may change the underlying quantum register, if the number of state qubits 111 | changes. 112 | 113 | Args: 114 | num_state_qubits: The new number of qubits. 115 | """ 116 | if num_state_qubits != self._num_state_qubits: 117 | self._invalidate() 118 | self._num_state_qubits = num_state_qubits 119 | self._reset_registers(num_state_qubits) 120 | 121 | @property 122 | def main_diag(self) -> float: 123 | """Return the entry in the main diagonal.""" 124 | return self._main_diag 125 | 126 | @main_diag.setter 127 | def main_diag(self, main_diag: float) -> None: 128 | """Set the entry in the main diagonal. 129 | Args: 130 | main_diag: The new entry in the main diagonal. 131 | """ 132 | self._main_diag = main_diag 133 | 134 | @property 135 | def off_diag(self) -> float: 136 | """Return the entry in the off diagonals.""" 137 | return self._off_diag 138 | 139 | @off_diag.setter 140 | def off_diag(self, off_diag: float) -> None: 141 | """Set the entry in the off diagonals. 142 | Args: 143 | off_diag: The new entry in the main diagonal. 144 | """ 145 | self._off_diag = off_diag 146 | 147 | @property 148 | def tolerance(self) -> float: 149 | """Return the error tolerance""" 150 | return self._tolerance 151 | 152 | @tolerance.setter 153 | def tolerance(self, tolerance: float) -> None: 154 | """Set the error tolerance. 155 | Args: 156 | tolerance: The new error tolerance. 157 | """ 158 | self._tolerance = tolerance 159 | 160 | @property 161 | def evolution_time(self) -> float: 162 | """Return the time of the evolution.""" 163 | return self._evolution_time 164 | 165 | @evolution_time.setter 166 | def evolution_time(self, evolution_time: float) -> None: 167 | """Set the time of the evolution and update the number of Trotter steps because the error 168 | tolerance is a function of the evolution time and the number of trotter steps. 169 | 170 | Args: 171 | evolution_time: The new time of the evolution. 172 | """ 173 | self._evolution_time = evolution_time 174 | # Update the number of trotter steps. Max 7 for now, upper bounds too loose. 175 | self.trotter_steps = int( 176 | np.ceil( 177 | np.sqrt( 178 | ((evolution_time * np.abs(self.off_diag)) ** 3) / 2 / self.tolerance 179 | ) 180 | ) 181 | ) 182 | 183 | @property 184 | def trotter_steps(self) -> int: 185 | """Return the number of trotter steps.""" 186 | return self._trotter_steps 187 | 188 | @trotter_steps.setter 189 | def trotter_steps(self, trotter_steps: int) -> None: 190 | """Set the number of trotter steps. 191 | Args: 192 | trotter_steps: The new number of trotter steps. 193 | """ 194 | self._trotter_steps = trotter_steps 195 | 196 | @property 197 | def matrix(self) -> np.ndarray: 198 | """Returns the tridiagonal Toeplitz matrix built according to the main and off diagonal 199 | entries.""" 200 | matrix = diags( 201 | [self.off_diag, self.main_diag, self.off_diag], 202 | [-1, 0, 1], 203 | shape=(2**self.num_state_qubits, 2**self.num_state_qubits), 204 | ).toarray() 205 | return matrix 206 | 207 | def eigs_bounds(self) -> Tuple[float, float]: 208 | """Return lower and upper bounds on the absolute eigenvalues of the matrix.""" 209 | n_b = 2**self.num_state_qubits 210 | 211 | # Calculate minimum and maximum of absolute value of eigenvalues 212 | # according to the formula for Toeplitz 3-diagonal matrices 213 | 214 | # For maximum it's enough to check border points of segment [1, n_b] 215 | candidate_eig_ids = [1, n_b] 216 | 217 | # Trying to add candidates near the minimum value of absolute eigenvalues 218 | # function abs(main_diag - 2 * off_diag * cos(i * pi / (nb + 1)) 219 | if abs(self.main_diag) < 2 * abs(self.off_diag): 220 | optimal_index = int( 221 | np.arccos(self.main_diag / 2 / self.off_diag) / np.pi * (n_b + 1) 222 | ) 223 | 224 | def add_candidate_index_if_valid(index_to_add: int) -> None: 225 | if 1 <= index_to_add <= n_b: 226 | candidate_eig_ids.append(index_to_add) 227 | 228 | add_candidate_index_if_valid(optimal_index - 1) 229 | add_candidate_index_if_valid(optimal_index) 230 | add_candidate_index_if_valid(optimal_index + 1) 231 | 232 | candidate_abs_eigs = np.abs( 233 | [ 234 | self.main_diag - 2 * self.off_diag * np.cos(eig_id * np.pi / (n_b + 1)) 235 | for eig_id in candidate_eig_ids 236 | ] 237 | ) 238 | 239 | lambda_min = np.min(candidate_abs_eigs) 240 | lambda_max = np.max(candidate_abs_eigs) 241 | return lambda_min, lambda_max 242 | 243 | def condition_bounds(self) -> Tuple[float, float]: 244 | """Return lower and upper bounds on the condition number of the matrix.""" 245 | matrix_array = self.matrix 246 | kappa = np.linalg.cond(matrix_array) 247 | return kappa, kappa 248 | 249 | def _check_configuration(self, raise_on_failure: bool = True) -> bool: 250 | """Check if the current configuration is valid.""" 251 | valid = True 252 | 253 | if self.trotter_steps < 1: 254 | valid = False 255 | if raise_on_failure: 256 | raise AttributeError( 257 | "The number of trotter steps should be a positive integer." 258 | ) 259 | return False 260 | 261 | return valid 262 | 263 | def _reset_registers(self, num_state_qubits: int) -> None: 264 | """Reset the quantum registers. 265 | 266 | Args: 267 | num_state_qubits: The number of qubits to represent the matrix. 268 | """ 269 | qr_state = QuantumRegister(num_state_qubits, "state") 270 | self.qregs = [qr_state] 271 | self._ancillas: List[AncillaRegister] = [] 272 | self._qubits = qr_state[:] 273 | 274 | if num_state_qubits > 1: 275 | qr_ancilla = AncillaRegister(max(1, num_state_qubits - 1)) 276 | self.add_register(qr_ancilla) 277 | 278 | def _build(self) -> None: 279 | """If not already built, build the circuit.""" 280 | if self._is_built: 281 | return 282 | 283 | super()._build() 284 | 285 | self.compose(self.power(1), inplace=True) 286 | 287 | def _main_diag_circ(self, theta: float = 1) -> QuantumCircuit: 288 | """Circuit implementing the matrix consisting of entries in the main diagonal. 289 | 290 | Args: 291 | theta: Scale factor for the main diagonal entries (e.g. evolution_time/trotter_steps). 292 | 293 | Returns: 294 | The quantum circuit implementing the matrix consisting of entries in the main diagonal. 295 | """ 296 | theta *= self.main_diag 297 | qc = QuantumCircuit(self.num_state_qubits, name="main_diag") 298 | qc.x(0) 299 | qc.p(theta, 0) 300 | qc.x(0) 301 | qc.p(theta, 0) 302 | 303 | # pylint: disable=unused-argument 304 | def control(num_ctrl_qubits=1, label=None, ctrl_state=None): 305 | qc_control = QuantumCircuit(self.num_state_qubits + 1, name="main_diag") 306 | qc_control.p(theta, 0) 307 | return qc_control 308 | 309 | qc.control = control 310 | return qc 311 | 312 | def _off_diag_circ(self, theta: float = 1) -> QuantumCircuit: 313 | """Circuit implementing the matrix consisting of entries in the off diagonals. 314 | 315 | Args: 316 | theta: Scale factor for the off diagonal entries (e.g. evolution_time/trotter_steps). 317 | 318 | Returns: 319 | The quantum circuit implementing the matrix consisting of entries in the off diagonals. 320 | """ 321 | theta *= self.off_diag 322 | 323 | qr = QuantumRegister(self.num_state_qubits) 324 | if self.num_state_qubits > 1: 325 | qr_ancilla = AncillaRegister(max(1, self.num_state_qubits - 2)) 326 | qc = QuantumCircuit(qr, qr_ancilla, name="off_diags") 327 | else: 328 | qc = QuantumCircuit(qr, name="off_diags") 329 | qr_ancilla = None 330 | 331 | qc.u(-2 * theta, 3 * np.pi / 2, np.pi / 2, qr[0]) 332 | 333 | for i in range(0, self.num_state_qubits - 1): 334 | q_controls = [] 335 | qc.cx(qr[i], qr[i + 1]) 336 | q_controls.append(qr[i + 1]) 337 | 338 | # Now we want controlled by 0 339 | qc.x(qr[i]) 340 | for j in range(i, 0, -1): 341 | qc.cx(qr[i], qr[j - 1]) 342 | q_controls.append(qr[j - 1]) 343 | qc.x(qr[i]) 344 | 345 | # Multicontrolled rotation 346 | if len(q_controls) > 1: 347 | ugate = UGate(-2 * theta, 3 * np.pi / 2, np.pi / 2) 348 | qc.append( 349 | MCMTVChain(ugate, len(q_controls), 1), 350 | q_controls[:] + [qr[i]] + qr_ancilla[: len(q_controls) - 1], 351 | ) 352 | else: 353 | qc.cu(-2 * theta, 3 * np.pi / 2, np.pi / 2, 0, q_controls[0], qr[i]) 354 | 355 | # Uncompute 356 | qc.x(qr[i]) 357 | for j in range(0, i): 358 | qc.cx(qr[i], qr[j]) 359 | qc.x(qr[i]) 360 | qc.cx(qr[i], qr[i + 1]) 361 | 362 | # pylint: disable=unused-argument 363 | def control(num_ctrl_qubits=1, label=None, ctrl_state=None): 364 | qr_state = QuantumRegister(self.num_state_qubits + 1) 365 | if self.num_state_qubits > 1: 366 | qr_ancilla = AncillaRegister(max(1, self.num_state_qubits - 1)) 367 | qc_control = QuantumCircuit(qr_state, qr_ancilla, name="off_diags") 368 | else: 369 | qc_control = QuantumCircuit(qr_state, name="off_diags") 370 | qr_ancilla = None 371 | # Control will be qr[0] 372 | q_control = qr_state[0] 373 | qr = qr_state[1:] 374 | qc_control.cu(-2 * theta, 3 * np.pi / 2, np.pi / 2, 0, q_control, qr[0]) 375 | 376 | for i in range(0, self.num_state_qubits - 1): 377 | q_controls = [] 378 | q_controls.append(q_control) 379 | qc_control.cx(qr[i], qr[i + 1]) 380 | q_controls.append(qr[i + 1]) 381 | 382 | # Now we want controlled by 0 383 | qc_control.x(qr[i]) 384 | for j in range(i, 0, -1): 385 | qc_control.cx(qr[i], qr[j - 1]) 386 | q_controls.append(qr[j - 1]) 387 | qc_control.x(qr[i]) 388 | 389 | # Multicontrolled x rotation 390 | if len(q_controls) > 1: 391 | ugate = UGate(-2 * theta, 3 * np.pi / 2, np.pi / 2) 392 | qc_control.append( 393 | MCMTVChain(ugate, len(q_controls), 1).to_gate(), 394 | q_controls[:] + [qr[i]] + qr_ancilla[: len(q_controls) - 1], 395 | ) 396 | else: 397 | qc_control.cu( 398 | -2 * theta, 3 * np.pi / 2, np.pi / 2, 0, q_controls[0], qr[i] 399 | ) 400 | 401 | # Uncompute 402 | qc_control.x(qr[i]) 403 | for j in range(0, i): 404 | qc_control.cx(qr[i], qr[j]) 405 | qc_control.x(qr[i]) 406 | qc_control.cx(qr[i], qr[i + 1]) 407 | return qc_control 408 | 409 | qc.control = control 410 | return qc 411 | 412 | def inverse(self): 413 | return TridiagonalToeplitz( 414 | self.num_state_qubits, 415 | self.main_diag, 416 | self.off_diag, 417 | evolution_time=-1 * self.evolution_time, 418 | ) 419 | 420 | def power(self, power: int, matrix_power: bool = False) -> QuantumCircuit: 421 | """Build powers of the circuit. 422 | 423 | Args: 424 | power: The power to raise this circuit to. 425 | matrix_power: If True, the circuit is converted to a matrix and then the 426 | matrix power is computed. If False, and ``power`` is a positive integer, 427 | the implementation defaults to ``repeat``. 428 | 429 | Returns: 430 | The quantum circuit implementing powers of the unitary. 431 | """ 432 | qc_raw = QuantumCircuit(self.num_state_qubits) 433 | 434 | # pylint: disable=unused-argument 435 | def control(num_ctrl_qubits=1, label=None, ctrl_state=None): 436 | qr_state = QuantumRegister(self.num_state_qubits + 1, "state") 437 | if self.num_state_qubits > 1: 438 | qr_ancilla = AncillaRegister(max(1, self.num_state_qubits - 1)) 439 | qc = QuantumCircuit(qr_state, qr_ancilla, name="exp(iHk)") 440 | else: 441 | qc = QuantumCircuit(qr_state, name="exp(iHk)") 442 | qr_ancilla = None 443 | # Control will be qr[0] 444 | q_control = qr_state[0] 445 | qr = qr_state[1:] 446 | # A1 commutes, so one application with evolution_time*2^{j} to the last qubit is enough 447 | qc.append( 448 | self._main_diag_circ(self.evolution_time * power).control().to_gate(), 449 | [q_control] + qr[:], 450 | ) 451 | 452 | # Update trotter steps to compensate the error 453 | trotter_steps_new = int(np.ceil(np.sqrt(power) * self.trotter_steps)) 454 | 455 | # exp(iA2t/2m) 456 | qc.u( 457 | self.off_diag * self.evolution_time * power / trotter_steps_new, 458 | 3 * np.pi / 2, 459 | np.pi / 2, 460 | qr[0], 461 | ) 462 | # for _ in range(power): 463 | for _ in range(0, trotter_steps_new): 464 | if qr_ancilla: 465 | qc.append( 466 | self._off_diag_circ( 467 | self.evolution_time * power / trotter_steps_new 468 | ) 469 | .control() 470 | .to_gate(), 471 | [q_control] + qr[:] + qr_ancilla[:], 472 | ) 473 | else: 474 | qc.append( 475 | self._off_diag_circ( 476 | self.evolution_time * power / trotter_steps_new 477 | ) 478 | .control() 479 | .to_gate(), 480 | [q_control] + qr[:], 481 | ) 482 | # exp(-iA2t/2m) 483 | qc.u( 484 | -self.off_diag * self.evolution_time * power / trotter_steps_new, 485 | 3 * np.pi / 2, 486 | np.pi / 2, 487 | qr[0], 488 | ) 489 | return qc 490 | 491 | qc_raw.control = control 492 | return qc_raw 493 | -------------------------------------------------------------------------------- /linear_solvers/numpy_linear_solver.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021, 2022. 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 | """The Numpy LinearSolver algorithm (classical).""" 13 | 14 | from typing import List, Union, Optional, Callable 15 | import numpy as np 16 | 17 | from qiskit import QuantumCircuit 18 | from qiskit.quantum_info import Operator, Statevector 19 | 20 | from .linear_solver import LinearSolverResult, LinearSolver 21 | from .observables.linear_system_observable import LinearSystemObservable 22 | 23 | # pylint: disable=too-few-public-methods 24 | 25 | 26 | class NumPyLinearSolver(LinearSolver): 27 | """The Numpy Linear Solver algorithm (classical). 28 | 29 | This linear system solver computes the exact value of the given observable(s) or the full 30 | solution vector if no observable is specified. 31 | 32 | Examples: 33 | 34 | .. jupyter-execute:: 35 | 36 | import numpy as np 37 | from quantum_linear_solvers.linear_solvers import NumPyLinearSolver 38 | from quantum_linear_solvers.linear_solvers.matrices import TridiagonalToeplitz 39 | from quantum_linear_solvers.linear_solvers.observables import MatrixFunctional 40 | 41 | matrix = TridiagonalToeplitz(2, 1, 1 / 3, trotter_steps=2) 42 | right_hand_side = [1.0, -2.1, 3.2, -4.3] 43 | observable = MatrixFunctional(1, 1 / 2) 44 | rhs = right_hand_side / np.linalg.norm(right_hand_side) 45 | 46 | np_solver = NumPyLinearSolver() 47 | solution = np_solver.solve(matrix, rhs, observable) 48 | result = solution.observable 49 | """ 50 | 51 | def solve( 52 | self, 53 | matrix: Union[np.ndarray, QuantumCircuit], 54 | vector: Union[np.ndarray, QuantumCircuit], 55 | observable: Optional[ 56 | Union[ 57 | LinearSystemObservable, 58 | List[LinearSystemObservable], 59 | ] 60 | ] = None, 61 | observable_circuit: Optional[ 62 | Union[QuantumCircuit, List[QuantumCircuit]] 63 | ] = None, 64 | post_processing: Optional[ 65 | Callable[[Union[float, List[float]], int, float], float] 66 | ] = None, 67 | ) -> LinearSolverResult: 68 | """Solve classically the linear system and compute the observable(s) 69 | 70 | Args: 71 | matrix: The matrix specifying the system, i.e. A in Ax=b. 72 | vector: The vector specifying the right hand side of the equation in Ax=b. 73 | observable: Optional information to be extracted from the solution. 74 | Default is the probability of success of the algorithm. 75 | observable_circuit: Optional circuit to be applied to the solution to extract 76 | information. Default is ``None``. 77 | post_processing: Optional function to compute the value of the observable. 78 | Default is the raw value of measuring the observable. 79 | 80 | Returns: 81 | The result of the linear system. 82 | """ 83 | # Check if either matrix or vector are QuantumCircuits and get the array from them 84 | if isinstance(vector, QuantumCircuit): 85 | vector = Statevector(vector).data 86 | if isinstance(matrix, QuantumCircuit): 87 | if hasattr(matrix, "matrix"): 88 | matrix = matrix.matrix 89 | else: 90 | matrix = Operator(matrix).data 91 | 92 | solution_vector = np.linalg.solve(matrix, vector) 93 | solution = LinearSolverResult() 94 | solution.state = solution_vector 95 | if observable is not None: 96 | if isinstance(observable, list): 97 | solution.observable = [] 98 | for obs in observable: 99 | solution.observable.append( 100 | obs.evaluate_classically(solution_vector) 101 | ) 102 | else: 103 | solution.observable = observable.evaluate_classically(solution_vector) 104 | solution.euclidean_norm = float(np.linalg.norm(solution_vector)) 105 | return solution 106 | -------------------------------------------------------------------------------- /linear_solvers/observables/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021, 2022. 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 | """Observables for Qiskit's linear solvers.""" 14 | 15 | from .linear_system_observable import LinearSystemObservable 16 | from .absolute_average import AbsoluteAverage 17 | from .matrix_functional import MatrixFunctional 18 | 19 | __all__ = ["LinearSystemObservable", "AbsoluteAverage", "MatrixFunctional"] 20 | -------------------------------------------------------------------------------- /linear_solvers/observables/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/linear_solvers/observables/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /linear_solvers/observables/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/linear_solvers/observables/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /linear_solvers/observables/__pycache__/absolute_average.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/linear_solvers/observables/__pycache__/absolute_average.cpython-38.pyc -------------------------------------------------------------------------------- /linear_solvers/observables/__pycache__/absolute_average.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/linear_solvers/observables/__pycache__/absolute_average.cpython-39.pyc -------------------------------------------------------------------------------- /linear_solvers/observables/__pycache__/linear_system_observable.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/linear_solvers/observables/__pycache__/linear_system_observable.cpython-38.pyc -------------------------------------------------------------------------------- /linear_solvers/observables/__pycache__/linear_system_observable.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/linear_solvers/observables/__pycache__/linear_system_observable.cpython-39.pyc -------------------------------------------------------------------------------- /linear_solvers/observables/__pycache__/matrix_functional.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/linear_solvers/observables/__pycache__/matrix_functional.cpython-38.pyc -------------------------------------------------------------------------------- /linear_solvers/observables/__pycache__/matrix_functional.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/linear_solvers/observables/__pycache__/matrix_functional.cpython-39.pyc -------------------------------------------------------------------------------- /linear_solvers/observables/absolute_average.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021, 2022. 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 | """The absolute value of the average of a linear system of equations solution.""" 14 | 15 | from typing import Union, List 16 | import numpy as np 17 | 18 | from qiskit import QuantumCircuit 19 | from qiskit.opflow import I, Z, TensoredOp 20 | from qiskit.quantum_info import Statevector 21 | 22 | from .linear_system_observable import LinearSystemObservable 23 | 24 | 25 | class AbsoluteAverage(LinearSystemObservable): 26 | r"""An observable for the absolute average of a linear system of equations solution. 27 | 28 | For a vector :math:`x=(x_1,...,x_N)`, the absolute average is defined as 29 | :math:`\abs{\frac{1}{N}\sum_{i=1}^{N}x_i}`. 30 | 31 | Examples: 32 | 33 | .. jupyter-execute:: 34 | 35 | import numpy as np 36 | from qiskit import QuantumCircuit 37 | from quantum_linear_solvers.linear_solvers.observables.absolute_average import \ 38 | AbsoluteAverage 39 | from qiskit.opflow import StateFn 40 | 41 | observable = AbsoluteAverage() 42 | vector = [1.0, -2.1, 3.2, -4.3] 43 | 44 | init_state = vector / np.linalg.norm(vector) 45 | num_qubits = int(np.log2(len(vector))) 46 | 47 | qc = QuantumCircuit(num_qubits) 48 | qc.isometry(init_state, list(range(num_qubits)), None) 49 | qc.append(observable.observable_circuit(num_qubits), list(range(num_qubits))) 50 | 51 | # Observable operator 52 | observable_op = observable.observable(num_qubits) 53 | state_vec = (~StateFn(observable_op) @ StateFn(qc)).eval() 54 | 55 | # Obtain result 56 | result = observable.post_processing(state_vec, num_qubits) 57 | 58 | # Obtain analytical evaluation 59 | exact = observable.evaluate_classically(init_state) 60 | """ 61 | 62 | def observable(self, num_qubits: int) -> Union[TensoredOp, List[TensoredOp]]: 63 | """The observable operator. 64 | 65 | Args: 66 | num_qubits: The number of qubits on which the observable will be applied. 67 | 68 | Returns: 69 | The observable as a sum of Pauli strings. 70 | """ 71 | zero_op = (I + Z) / 2 72 | return TensoredOp(num_qubits * [zero_op]) 73 | 74 | def observable_circuit( 75 | self, num_qubits: int 76 | ) -> Union[QuantumCircuit, List[QuantumCircuit]]: 77 | """The circuit implementing the absolute average observable. 78 | 79 | Args: 80 | num_qubits: The number of qubits on which the observable will be applied. 81 | 82 | Returns: 83 | The observable as a QuantumCircuit. 84 | """ 85 | qc = QuantumCircuit(num_qubits) 86 | qc.h(qc.qubits) 87 | return qc 88 | 89 | def post_processing( 90 | self, solution: Union[float, List[float]], num_qubits: int, scaling: float = 1 91 | ) -> float: 92 | """Evaluates the absolute average on the solution to the linear system. 93 | 94 | Args: 95 | solution: The probability calculated from the circuit and the observable. 96 | num_qubits: The number of qubits where the observable was applied. 97 | scaling: Scaling of the solution. 98 | 99 | Returns: 100 | The value of the absolute average. 101 | 102 | Raises: 103 | ValueError: If the input is not in the correct format. 104 | """ 105 | if isinstance(solution, list): 106 | if len(solution) == 1: 107 | solution = solution[0] 108 | else: 109 | raise ValueError( 110 | "Solution probability must be given as a single value." 111 | ) 112 | 113 | return np.real(np.sqrt(solution / (2**num_qubits)) / scaling) 114 | 115 | def evaluate_classically( 116 | self, solution: Union[np.ndarray, QuantumCircuit] 117 | ) -> float: 118 | """Evaluates the given observable on the solution to the linear system. 119 | 120 | Args: 121 | solution: The solution to the system as a numpy array or the circuit that prepares it. 122 | 123 | Returns: 124 | The value of the observable. 125 | """ 126 | # Check if it is QuantumCircuits and get the array from them 127 | if isinstance(solution, QuantumCircuit): 128 | solution = Statevector(solution).data 129 | return np.abs(np.mean(solution)) 130 | -------------------------------------------------------------------------------- /linear_solvers/observables/linear_system_observable.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021, 2022. 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 | """An abstract class for linear systems solvers in Qiskit's aqua module.""" 14 | 15 | from abc import ABC, abstractmethod 16 | from typing import Union, List 17 | import numpy as np 18 | 19 | from qiskit import QuantumCircuit 20 | from qiskit.opflow import TensoredOp 21 | 22 | 23 | class LinearSystemObservable(ABC): 24 | """An abstract class for linear system observables in Qiskit.""" 25 | 26 | @abstractmethod 27 | def observable(self, num_qubits: int) -> Union[TensoredOp, List[TensoredOp]]: 28 | """The observable operator. 29 | 30 | Args: 31 | num_qubits: The number of qubits on which the observable will be applied. 32 | 33 | Returns: 34 | The observable as a sum of Pauli strings. 35 | """ 36 | raise NotImplementedError 37 | 38 | @abstractmethod 39 | def observable_circuit( 40 | self, num_qubits: int 41 | ) -> Union[QuantumCircuit, List[QuantumCircuit]]: 42 | """The circuit implementing the observable. 43 | 44 | Args: 45 | num_qubits: The number of qubits on which the observable will be applied. 46 | 47 | Returns: 48 | The observable as a QuantumCircuit. 49 | """ 50 | raise NotImplementedError 51 | 52 | @abstractmethod 53 | def post_processing( 54 | self, solution: Union[float, List[float]], num_qubits: int, scaling: float = 1 55 | ) -> float: 56 | """Evaluates the given observable on the solution to the linear system. 57 | 58 | Args: 59 | solution: The probability calculated from the circuit and the observable. 60 | num_qubits: The number of qubits where the observable was applied. 61 | scaling: Scaling of the solution. 62 | 63 | Returns: 64 | The value of the observable. 65 | """ 66 | raise NotImplementedError 67 | 68 | @abstractmethod 69 | def evaluate_classically( 70 | self, solution: Union[np.ndarray, QuantumCircuit] 71 | ) -> float: 72 | """Calculates the analytical value of the given observable from the solution vector to the 73 | linear system. 74 | 75 | Args: 76 | solution: The solution to the system as a numpy array or the circuit that prepares it. 77 | 78 | Returns: 79 | The value of the observable. 80 | """ 81 | raise NotImplementedError 82 | -------------------------------------------------------------------------------- /linear_solvers/observables/matrix_functional.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021, 2022. 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 | """The matrix functional of the vector solution to the linear systems.""" 14 | 15 | from typing import Union, List 16 | import numpy as np 17 | from scipy.sparse import diags 18 | 19 | from qiskit import QuantumCircuit 20 | from qiskit.quantum_info import Statevector 21 | from qiskit.opflow import I, Z, TensoredOp 22 | 23 | from .linear_system_observable import LinearSystemObservable 24 | 25 | 26 | class MatrixFunctional(LinearSystemObservable): 27 | """A class for the matrix functional of the vector solution to the linear systems. 28 | 29 | Examples: 30 | 31 | .. jupyter-execute:: 32 | 33 | import numpy as np 34 | from qiskit import QuantumCircuit 35 | from quantum_linear_solvers.linear_solvers.observables.matrix_functional import \ 36 | MatrixFunctional 37 | from qiskit.transpiler.passes import RemoveResetInZeroState 38 | from qiskit.opflow import StateFn 39 | 40 | tpass = RemoveResetInZeroState() 41 | 42 | vector = [1.0, -2.1, 3.2, -4.3] 43 | observable = MatrixFunctional(1, -1 / 3) 44 | 45 | init_state = vector / np.linalg.norm(vector) 46 | num_qubits = int(np.log2(len(vector))) 47 | 48 | # Get observable circuits 49 | obs_circuits = observable.observable_circuit(num_qubits) 50 | qcs = [] 51 | for obs_circ in obs_circuits: 52 | qc = QuantumCircuit(num_qubits) 53 | qc.isometry(init_state, list(range(num_qubits)), None) 54 | qc.append(obs_circ, list(range(num_qubits))) 55 | qcs.append(tpass(qc.decompose())) 56 | 57 | # Get observables 58 | observable_ops = observable.observable(num_qubits) 59 | state_vecs = [] 60 | # First is the norm 61 | state_vecs.append((~StateFn(observable_ops[0]) @ StateFn(qcs[0])).eval()) 62 | for i in range(1, len(observable_ops), 2): 63 | state_vecs += [(~StateFn(observable_ops[i]) @ StateFn(qcs[i])).eval(), 64 | (~StateFn(observable_ops[i + 1]) @ StateFn(qcs[i + 1])).eval()] 65 | 66 | # Obtain result 67 | result = observable.post_processing(state_vecs, num_qubits) 68 | 69 | # Obtain analytical evaluation 70 | exact = observable.evaluate_classically(init_state) 71 | """ 72 | 73 | def __init__(self, main_diag: float, off_diag: float) -> None: 74 | """ 75 | Args: 76 | main_diag: The main diagonal of the tridiagonal Toeplitz symmetric matrix to compute 77 | the functional. 78 | off_diag: The off diagonal of the tridiagonal Toeplitz symmetric matrix to compute 79 | the functional. 80 | """ 81 | self._main_diag = main_diag 82 | self._off_diag = off_diag 83 | 84 | def observable(self, num_qubits: int) -> Union[TensoredOp, List[TensoredOp]]: 85 | """The observable operators. 86 | 87 | Args: 88 | num_qubits: The number of qubits on which the observable will be applied. 89 | 90 | Returns: 91 | The observable as a list of sums of Pauli strings. 92 | """ 93 | zero_op = (I + Z) / 2 94 | one_op = (I - Z) / 2 95 | observables = [] 96 | # First we measure the norm of x 97 | observables.append(I ^ num_qubits) 98 | for i in range(num_qubits): 99 | j = num_qubits - i - 1 100 | 101 | # TODO this if can be removed once the bug in Opflow is fixed where 102 | # TensoredOp([X, TensoredOp([])]).eval() ends up in infinite recursion 103 | if i > 0: 104 | observables += [ 105 | (I ^ j) ^ zero_op ^ TensoredOp(i * [one_op]), 106 | (I ^ j) ^ one_op ^ TensoredOp(i * [one_op]), 107 | ] 108 | else: 109 | observables += [(I ^ j) ^ zero_op, (I ^ j) ^ one_op] 110 | 111 | return observables 112 | 113 | def observable_circuit( 114 | self, num_qubits: int 115 | ) -> Union[QuantumCircuit, List[QuantumCircuit]]: 116 | """The circuits to implement the matrix functional observable. 117 | 118 | Args: 119 | num_qubits: The number of qubits on which the observable will be applied. 120 | 121 | Returns: 122 | The observable as a list of QuantumCircuits. 123 | """ 124 | qcs = [] 125 | # Again, the first value in the list will correspond to the norm of x 126 | qcs.append(QuantumCircuit(num_qubits)) 127 | for i in range(0, num_qubits): 128 | qc = QuantumCircuit(num_qubits) 129 | for j in range(0, i): 130 | qc.cx(i, j) 131 | qc.h(i) 132 | qcs += [qc, qc] 133 | 134 | return qcs 135 | 136 | def post_processing( 137 | self, solution: Union[float, List[float]], num_qubits: int, scaling: float = 1 138 | ) -> float: 139 | """Evaluates the matrix functional on the solution to the linear system. 140 | 141 | Args: 142 | solution: The list of probabilities calculated from the circuit and the observable. 143 | num_qubits: The number of qubits where the observable was applied. 144 | scaling: Scaling of the solution. 145 | 146 | Returns: 147 | The value of the absolute average. 148 | 149 | Raises: 150 | ValueError: If the input is not in the correct format. 151 | """ 152 | if not isinstance(solution, list): 153 | raise ValueError("Solution probabilities must be given in list form.") 154 | 155 | # Calculate the value from the off-diagonal elements 156 | off_val = 0.0 157 | for i in range(1, len(solution), 2): 158 | off_val += (solution[i] - solution[i + 1]) / (scaling**2) 159 | main_val = solution[0] / (scaling**2) 160 | return np.real(self._main_diag * main_val + self._off_diag * off_val) 161 | 162 | def evaluate_classically( 163 | self, solution: Union[np.ndarray, QuantumCircuit] 164 | ) -> float: 165 | """Evaluates the given observable on the solution to the linear system. 166 | 167 | Args: 168 | solution: The solution to the system as a numpy array or the circuit that prepares it. 169 | 170 | Returns: 171 | The value of the observable. 172 | """ 173 | # Check if it is QuantumCircuits and get the array from them 174 | if isinstance(solution, QuantumCircuit): 175 | solution = Statevector(solution).data 176 | 177 | matrix = diags( 178 | [self._off_diag, self._main_diag, self._off_diag], 179 | [-1, 0, 1], 180 | shape=(len(solution), len(solution)), 181 | ).toarray() 182 | 183 | return np.dot(solution.transpose(), np.dot(matrix, solution)) 184 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel"] 3 | build-backend = "setuptools.build_meta" -------------------------------------------------------------------------------- /quantum_linear_solvers.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: quantum-linear-solvers 3 | Version: 0.1.dev2+g81a3efb 4 | Summary: Quantum linear solvers package 5 | License-File: LICENSE 6 | 7 | # quantum_linear_solvers 8 | Contains classical and quantum algorithms to solve systems of linear equations such as the HHL algorithm. 9 | -------------------------------------------------------------------------------- /quantum_linear_solvers.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | LICENSE 2 | README.md 3 | pyproject.toml 4 | requirements-dev.txt 5 | requirements.txt 6 | setup.py 7 | dist/quantum_linear_solvers-0.1.dev1+g17fb9c0.tar.gz 8 | linear_solvers/__init__.py 9 | linear_solvers/hhl.py 10 | linear_solvers/linear_solver.py 11 | linear_solvers/numpy_linear_solver.py 12 | linear_solvers/__pycache__/__init__.cpython-38.pyc 13 | linear_solvers/__pycache__/__init__.cpython-39.pyc 14 | linear_solvers/__pycache__/hhl.cpython-38.pyc 15 | linear_solvers/__pycache__/hhl.cpython-39.pyc 16 | linear_solvers/__pycache__/linear_solver.cpython-38.pyc 17 | linear_solvers/__pycache__/linear_solver.cpython-39.pyc 18 | linear_solvers/__pycache__/numpy_linear_solver.cpython-38.pyc 19 | linear_solvers/__pycache__/numpy_linear_solver.cpython-39.pyc 20 | linear_solvers/matrices/__init__.py 21 | linear_solvers/matrices/linear_system_matrix.py 22 | linear_solvers/matrices/numpy_matrix.py 23 | linear_solvers/matrices/tridiagonal_toeplitz.py 24 | linear_solvers/matrices/__pycache__/__init__.cpython-38.pyc 25 | linear_solvers/matrices/__pycache__/__init__.cpython-39.pyc 26 | linear_solvers/matrices/__pycache__/linear_system_matrix.cpython-38.pyc 27 | linear_solvers/matrices/__pycache__/linear_system_matrix.cpython-39.pyc 28 | linear_solvers/matrices/__pycache__/numpy_matrix.cpython-38.pyc 29 | linear_solvers/matrices/__pycache__/numpy_matrix.cpython-39.pyc 30 | linear_solvers/matrices/__pycache__/tridiagonal_toeplitz.cpython-38.pyc 31 | linear_solvers/matrices/__pycache__/tridiagonal_toeplitz.cpython-39.pyc 32 | linear_solvers/observables/__init__.py 33 | linear_solvers/observables/absolute_average.py 34 | linear_solvers/observables/linear_system_observable.py 35 | linear_solvers/observables/matrix_functional.py 36 | linear_solvers/observables/__pycache__/__init__.cpython-38.pyc 37 | linear_solvers/observables/__pycache__/__init__.cpython-39.pyc 38 | linear_solvers/observables/__pycache__/absolute_average.cpython-38.pyc 39 | linear_solvers/observables/__pycache__/absolute_average.cpython-39.pyc 40 | linear_solvers/observables/__pycache__/linear_system_observable.cpython-38.pyc 41 | linear_solvers/observables/__pycache__/linear_system_observable.cpython-39.pyc 42 | linear_solvers/observables/__pycache__/matrix_functional.cpython-38.pyc 43 | linear_solvers/observables/__pycache__/matrix_functional.cpython-39.pyc 44 | quantum_linear_solvers.egg-info/PKG-INFO 45 | quantum_linear_solvers.egg-info/SOURCES.txt 46 | quantum_linear_solvers.egg-info/dependency_links.txt 47 | quantum_linear_solvers.egg-info/requires.txt 48 | quantum_linear_solvers.egg-info/top_level.txt 49 | test/test_linear_solvers.py -------------------------------------------------------------------------------- /quantum_linear_solvers.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /quantum_linear_solvers.egg-info/requires.txt: -------------------------------------------------------------------------------- 1 | importlib_metadata 2 | mapomatic==0.6 3 | mthree==0.24 4 | pylatexenc 5 | qiskit-aer 6 | qiskit-ibmq-provider 7 | qiskit-terra==0.21 8 | -------------------------------------------------------------------------------- /quantum_linear_solvers.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | linear_solvers 2 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | black[jupyter] 2 | coverage 3 | ddt>=1.2.0,!=1.4.0,!=1.4.3 4 | mypy==0.961 5 | jupyter-sphinx 6 | nbsphinx 7 | pylint==2.14 8 | pytest==7.1 9 | reno 10 | sphinx-autodoc-typehints 11 | treon==0.1.3 -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | importlib_metadata 2 | mapomatic==0.6 3 | mthree==0.24 4 | pylatexenc 5 | qiskit-aer 6 | qiskit-ibmq-provider 7 | qiskit-terra>=0.21 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | 3 | name = "quantum_linear_solvers" 4 | version = "0.0.1" 5 | description = ( 6 | "Quantum linear solvers package" 7 | ) 8 | 9 | with open("README.md") as f: 10 | long_description = f.read() 11 | 12 | with open("requirements.txt") as f: 13 | install_requires = f.read() 14 | 15 | setup( 16 | name=name, 17 | version=version, 18 | description=description, 19 | long_description=long_description, 20 | install_requires=install_requires, 21 | packages=find_packages(), 22 | setup_requires=["setuptools_scm"], 23 | use_scm_version=True, 24 | include_package_data=True, 25 | ) 26 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2022. 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 | -------------------------------------------------------------------------------- /test/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/test/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /test/__pycache__/test_linear_solvers.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anedumla/quantum_linear_solvers/08bf4f6673308568b5654e5d3fde9f8df5d887d7/test/__pycache__/test_linear_solvers.cpython-38.pyc -------------------------------------------------------------------------------- /test/test_linear_solvers.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2021, 2022. 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 | """Test the quantum linear system solver algorithm.""" 14 | 15 | import unittest 16 | from scipy.linalg import expm 17 | import numpy as np 18 | from ddt import ddt, idata, unpack 19 | from qiskit import BasicAer, QuantumCircuit 20 | from linear_solvers.hhl import HHL 21 | from linear_solvers.matrices.tridiagonal_toeplitz import TridiagonalToeplitz 22 | from linear_solvers.matrices.numpy_matrix import NumPyMatrix 23 | from linear_solvers.observables.absolute_average import AbsoluteAverage 24 | from linear_solvers.observables.matrix_functional import MatrixFunctional 25 | from qiskit.circuit.library.arithmetic.exact_reciprocal import ExactReciprocal 26 | from qiskit.quantum_info import Operator, partial_trace 27 | from qiskit.opflow import I, Z, StateFn 28 | from qiskit.utils import QuantumInstance 29 | from qiskit import quantum_info 30 | 31 | 32 | @ddt 33 | class TestMatrices(unittest.TestCase): 34 | """Tests based on the matrices classes. 35 | 36 | This class tests 37 | * the constructed circuits 38 | """ 39 | 40 | @idata( 41 | [ 42 | [TridiagonalToeplitz(2, 1, -1 / 3)], 43 | [TridiagonalToeplitz(3, 2, 1), 1.1, 3], 44 | [ 45 | NumPyMatrix( 46 | np.array( 47 | [ 48 | [1 / 2, 1 / 6, 0, 0], 49 | [1 / 6, 1 / 2, 1 / 6, 0], 50 | [0, 1 / 6, 1 / 2, 1 / 6], 51 | [0, 0, 1 / 6, 1 / 2], 52 | ] 53 | ) 54 | ) 55 | ], 56 | ] 57 | ) 58 | @unpack 59 | def test_matrices(self, matrix, time=1.0, power=1): 60 | """Test the different matrix classes.""" 61 | matrix.evolution_time = time 62 | 63 | num_qubits = matrix.num_state_qubits 64 | pow_circ = matrix.power(power).control() 65 | circ_qubits = pow_circ.num_qubits 66 | qc = QuantumCircuit(circ_qubits) 67 | qc.append(matrix.power(power).control(), list(range(circ_qubits))) 68 | # extract the parts of the circuit matrix corresponding to TridiagonalToeplitz 69 | zero_op = (I + Z) / 2 70 | one_op = (I - Z) / 2 71 | proj = Operator( 72 | (zero_op ^ pow_circ.num_ancillas) ^ (I ^ num_qubits) ^ one_op 73 | ).data 74 | circ_matrix = Operator(qc).data 75 | approx_exp = partial_trace( 76 | np.dot(proj, circ_matrix), [0] + list(range(num_qubits + 1, circ_qubits)) 77 | ).data 78 | 79 | exact_exp = expm(1j * matrix.evolution_time * power * matrix.matrix) 80 | np.testing.assert_array_almost_equal(approx_exp, exact_exp, decimal=2) 81 | 82 | @idata( 83 | [ 84 | [TridiagonalToeplitz(2, 1.5, 2.5)], 85 | [TridiagonalToeplitz(4, -1, 1.6)], 86 | ] 87 | ) 88 | @unpack 89 | def test_eigs_bounds(self, matrix): 90 | """Test the capability of TridiagonalToeplitz matrix class 91 | to find accurate absolute eigenvalues bounds.""" 92 | 93 | matrix_lambda_min, matrix_lambda_max = matrix.eigs_bounds() 94 | 95 | numpy_matrix = matrix.matrix 96 | eigenvalues, _ = np.linalg.eig(numpy_matrix) 97 | abs_eigenvalues = np.abs(eigenvalues) 98 | exact_lambda_min = np.min(abs_eigenvalues) 99 | exact_lambda_max = np.max(abs_eigenvalues) 100 | 101 | np.testing.assert_almost_equal(matrix_lambda_min, exact_lambda_min, decimal=6) 102 | np.testing.assert_almost_equal(matrix_lambda_max, exact_lambda_max, decimal=6) 103 | 104 | 105 | @ddt 106 | class TestObservables(unittest.TestCase): 107 | """Tests based on the observables classes. 108 | 109 | This class tests 110 | * the constructed circuits 111 | """ 112 | 113 | @idata( 114 | [ 115 | [AbsoluteAverage(), [1.0, -2.1, 3.2, -4.3]], 116 | [AbsoluteAverage(), [-9 / 4, -0.3, 8 / 7, 10, -5, 11.1, 13 / 11, -27 / 12]], 117 | ] 118 | ) 119 | @unpack 120 | def test_absolute_average(self, observable, vector): 121 | """Test the absolute average observable.""" 122 | init_state = vector / np.linalg.norm(vector) 123 | num_qubits = int(np.log2(len(vector))) 124 | 125 | qc = QuantumCircuit(num_qubits) 126 | qc.isometry(init_state, list(range(num_qubits)), None) 127 | qc.append(observable.observable_circuit(num_qubits), list(range(num_qubits))) 128 | 129 | # Observable operator 130 | observable_op = observable.observable(num_qubits) 131 | state_vec = (~StateFn(observable_op) @ StateFn(qc)).eval() 132 | 133 | # Obtain result 134 | result = observable.post_processing(state_vec, num_qubits) 135 | 136 | # Obtain analytical evaluation 137 | exact = observable.evaluate_classically(init_state) 138 | 139 | np.testing.assert_almost_equal(result, exact, decimal=2) 140 | 141 | @idata( 142 | [ 143 | [MatrixFunctional(1, -1 / 3), [1.0, -2.1, 3.2, -4.3]], 144 | [ 145 | MatrixFunctional(2 / 3, 11 / 7), 146 | [-9 / 4, -0.3, 8 / 7, 10, -5, 11.1, 13 / 11, -27 / 12], 147 | ], 148 | ] 149 | ) 150 | @unpack 151 | def test_matrix_functional(self, observable, vector): 152 | """Test the matrix functional class.""" 153 | from qiskit.transpiler.passes import RemoveResetInZeroState 154 | 155 | tpass = RemoveResetInZeroState() 156 | 157 | init_state = vector / np.linalg.norm(vector) 158 | num_qubits = int(np.log2(len(vector))) 159 | 160 | # Get observable circuits 161 | obs_circuits = observable.observable_circuit(num_qubits) 162 | qcs = [] 163 | for obs_circ in obs_circuits: 164 | qc = QuantumCircuit(num_qubits) 165 | qc.isometry(init_state, list(range(num_qubits)), None) 166 | qc.append(obs_circ, list(range(num_qubits))) 167 | qcs.append(tpass(qc.decompose())) 168 | 169 | # Get observables 170 | observable_ops = observable.observable(num_qubits) 171 | state_vecs = [] 172 | # First is the norm 173 | state_vecs.append((~StateFn(observable_ops[0]) @ StateFn(qcs[0])).eval()) 174 | for i in range(1, len(observable_ops), 2): 175 | state_vecs += [ 176 | (~StateFn(observable_ops[i]) @ StateFn(qcs[i])).eval(), 177 | (~StateFn(observable_ops[i + 1]) @ StateFn(qcs[i + 1])).eval(), 178 | ] 179 | 180 | # Obtain result 181 | result = observable.post_processing(state_vecs, num_qubits) 182 | 183 | # Obtain analytical evaluation 184 | exact = observable.evaluate_classically(init_state) 185 | 186 | np.testing.assert_almost_equal(result, exact, decimal=2) 187 | 188 | 189 | @ddt 190 | class TestReciprocal(unittest.TestCase): 191 | """Tests based on the reciprocal classes. 192 | 193 | This class tests 194 | * the constructed circuits 195 | """ 196 | 197 | @idata([[2, 0.1, False], [3, 1 / 9, True]]) 198 | @unpack 199 | def test_exact_reciprocal(self, num_qubits, scaling, neg_vals): 200 | """Test the ExactReciprocal class.""" 201 | reciprocal = ExactReciprocal(num_qubits + neg_vals, scaling, neg_vals) 202 | 203 | qc = QuantumCircuit(num_qubits + 1 + neg_vals) 204 | qc.h(list(range(num_qubits))) 205 | # If negative eigenvalues, set the sign qubit to 1 206 | if neg_vals: 207 | qc.x(num_qubits) 208 | qc.append(reciprocal, list(range(num_qubits + 1 + neg_vals))) 209 | 210 | # Create the operator 0 211 | state_vec = quantum_info.Statevector.from_instruction(qc).data[ 212 | -(2**num_qubits) : 213 | ] 214 | 215 | # Remove the factor from the hadamards 216 | state_vec *= np.sqrt(2) ** num_qubits 217 | 218 | # Analytic value 219 | exact = [] 220 | for i in range(0, 2**num_qubits): 221 | if i == 0: 222 | exact.append(0) 223 | else: 224 | if neg_vals: 225 | exact.append(-scaling / (1 - i / (2**num_qubits))) 226 | else: 227 | exact.append(scaling * (2**num_qubits) / i) 228 | 229 | np.testing.assert_array_almost_equal(state_vec, exact, decimal=2) 230 | 231 | 232 | @ddt 233 | class TestLinearSolver(unittest.TestCase): 234 | """Tests based on the linear solvers classes. 235 | 236 | This class tests 237 | * the constructed circuits 238 | """ 239 | 240 | @idata( 241 | [ 242 | [ 243 | TridiagonalToeplitz(2, 1, 1 / 3, trotter_steps=2), 244 | [1.0, -2.1, 3.2, -4.3], 245 | MatrixFunctional(1, 1 / 2), 246 | ], 247 | [ 248 | np.array( 249 | [ 250 | [0, 0, 1.585, 0], 251 | [0, 0, -0.585, 1], 252 | [1.585, -0.585, 0, 0], 253 | [0, 1, 0, 0], 254 | ] 255 | ), 256 | [1.0, 0, 0, 0], 257 | MatrixFunctional(1, 1 / 2), 258 | ], 259 | [ 260 | [ 261 | [1 / 2, 1 / 6, 0, 0], 262 | [1 / 6, 1 / 2, 1 / 6, 0], 263 | [0, 1 / 6, 1 / 2, 1 / 6], 264 | [0, 0, 1 / 6, 1 / 2], 265 | ], 266 | [1.0, -2.1, 3.2, -4.3], 267 | MatrixFunctional(1, 1 / 2), 268 | ], 269 | [ 270 | np.array([[82, 34], [34, 58]]), 271 | np.array([[1], [0]]), 272 | AbsoluteAverage(), 273 | 3, 274 | ], 275 | [ 276 | TridiagonalToeplitz(3, 1, -1 / 2, trotter_steps=2), 277 | [-9 / 4, -0.3, 8 / 7, 10, -5, 11.1, 13 / 11, -27 / 12], 278 | AbsoluteAverage(), 279 | ], 280 | ] 281 | ) 282 | @unpack 283 | def test_hhl(self, matrix, right_hand_side, observable, decimal=1): 284 | """Test the HHL class.""" 285 | if isinstance(matrix, QuantumCircuit): 286 | num_qubits = matrix.num_state_qubits 287 | elif isinstance(matrix, (np.ndarray)): 288 | num_qubits = int(np.log2(matrix.shape[0])) 289 | elif isinstance(matrix, list): 290 | num_qubits = int(np.log2(len(matrix))) 291 | 292 | rhs = right_hand_side / np.linalg.norm(right_hand_side) 293 | 294 | # Initial state circuit 295 | qc = QuantumCircuit(num_qubits) 296 | qc.isometry(rhs, list(range(num_qubits)), None) 297 | 298 | hhl = HHL() 299 | solution = hhl.solve(matrix, qc, observable) 300 | approx_result = solution.observable 301 | 302 | # Calculate analytical value 303 | if isinstance(matrix, QuantumCircuit): 304 | exact_x = np.dot(np.linalg.inv(matrix.matrix), rhs) 305 | elif isinstance(matrix, (list, np.ndarray)): 306 | if isinstance(matrix, list): 307 | matrix = np.array(matrix) 308 | exact_x = np.dot(np.linalg.inv(matrix), rhs) 309 | exact_result = observable.evaluate_classically(exact_x) 310 | 311 | np.testing.assert_almost_equal(approx_result, exact_result, decimal=decimal) 312 | 313 | def test_hhl_qi(self): 314 | """Test the HHL quantum instance getter and setter.""" 315 | hhl = HHL() 316 | self.assertIsNone(hhl.quantum_instance) # Defaults to None 317 | 318 | # First set a valid quantum instance and check via getter 319 | qinst = QuantumInstance(backend=BasicAer.get_backend("qasm_simulator")) 320 | hhl.quantum_instance = qinst 321 | self.assertEqual(hhl.quantum_instance, qinst) 322 | 323 | # Now set quantum instance back to None and check via getter 324 | hhl.quantum_instance = None 325 | self.assertIsNone(hhl.quantum_instance) 326 | 327 | 328 | if __name__ == "__main__": 329 | unittest.main() 330 | --------------------------------------------------------------------------------