diff --git a/poetry.lock b/poetry.lock index 71a8fc2..5d9a123 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2697,17 +2697,17 @@ cffi = {version = "*", markers = "implementation_name == \"pypy\""} [[package]] name = "qiskit" -version = "0.44.3" +version = "0.45.1" description = "Software for developing quantum computing programs" optional = false python-versions = ">=3.8" files = [ - {file = "qiskit-0.44.3-py3-none-any.whl", hash = "sha256:947a02f581c7a5eba7c739c840924cf8996be7205a6ee3720a1422deb0b781fe"}, - {file = "qiskit-0.44.3.tar.gz", hash = "sha256:bc3447fda682cff5789125b51936450781bd9a73b817db8610f23ba16c1b8912"}, + {file = "qiskit-0.45.1-py3-none-any.whl", hash = "sha256:86a757f38f4934a0668cafd3bf0a18e3abaeac697d14f0a9e155cfab2d8c1817"}, + {file = "qiskit-0.45.1.tar.gz", hash = "sha256:1ae868c514db3e35c0a75d8ec08002491b9301586647637f11f744f8a376bb9e"}, ] [package.dependencies] -qiskit-terra = "0.25.3" +qiskit-terra = "0.45.1" [package.extras] all = ["qiskit-terra[all]"] @@ -2944,43 +2944,42 @@ visualization = ["ipython (>=5.0.0)", "ipyvue (>=1.4.1)", "ipyvuetify (>=1.1)", [[package]] name = "qiskit-terra" -version = "0.25.3" +version = "0.45.1" description = "Software for developing quantum computing programs" optional = false python-versions = ">=3.8" files = [ - {file = "qiskit-terra-0.25.3.tar.gz", hash = "sha256:1540a63d9771021724d0587975bd6b9fe0210852f4d96df7ec86305e0b09608c"}, - {file = "qiskit_terra-0.25.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:7e824d0f7c346160c816723abacd5d56497354044c3b78e511c4bc82252ec1a0"}, - {file = "qiskit_terra-0.25.3-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ab343e992a87ec7e3931fa4734bb4b60c10307920046965463e880aca80e3fa8"}, - {file = "qiskit_terra-0.25.3-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:89e359fa9f0124e596e62cc9ba23440507278fb27029876d611f8c116e5a7668"}, - {file = "qiskit_terra-0.25.3-cp38-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ea164fc9843c309affd804ddf6445f786ae4019006dd77ec60765cf46b91599"}, - {file = "qiskit_terra-0.25.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b11517d5ba89f6b53ff29aaa6110de2cfc9458ae5c8a0dbe6b5e84ca0e1de8b5"}, - {file = "qiskit_terra-0.25.3-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66398c780a29e3118c740ae09a69de4cdb80e12bc58ac9e7fee28623f82ac6a5"}, - {file = "qiskit_terra-0.25.3-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d547d217608f9f3adf7175f240c52be4ddad1bcc857e4a413b5c23b03b2ff642"}, - {file = "qiskit_terra-0.25.3-cp38-abi3-win32.whl", hash = "sha256:85cb5f3e4789e1d5d1c0e35263bd519679d8d27c92c330429f36b213cae72834"}, - {file = "qiskit_terra-0.25.3-cp38-abi3-win_amd64.whl", hash = "sha256:577480f6c3e0593ff8f826d82221c142ea77229a42c893054be5e65e5d100e1d"}, + {file = "qiskit-terra-0.45.1.tar.gz", hash = "sha256:4ed0cf0d5f275276a27135b9d02f599d0c59c0c1c4286c2392314558481ed736"}, + {file = "qiskit_terra-0.45.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:edb6907b648ba8f2dd2c7409849afa7be7e2edb350014d3e78fc6d03505b205f"}, + {file = "qiskit_terra-0.45.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:df34e6cbfc566b3411272de5dcc1c4f20c9d2d4af2b2e5f2f821d075bc05f5f8"}, + {file = "qiskit_terra-0.45.1-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:4f3cade24c2dba896686fdd27986304a8784bf4c2e55181853f08ff1c766019c"}, + {file = "qiskit_terra-0.45.1-cp38-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dde0571a3e0be9ced6a50240b28d9e048309aa712fcdb37571c090e7845bed9e"}, + {file = "qiskit_terra-0.45.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e29d1264b890619d0a0ff2d0cdb244cb12ea27217c47a8f1ef2c8eaa9bd6b94"}, + {file = "qiskit_terra-0.45.1-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47f9e7e25b0b04b73a8475865a2b0850bc5104536eaca05278657933167a7174"}, + {file = "qiskit_terra-0.45.1-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9081e1731d0fe80619612073f9cfbcfbd4cd64c01c843ce936f4eb17876cb020"}, + {file = "qiskit_terra-0.45.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f8500c757386354fbb1d9424395541b90cc72d8b0dadaf2606d01190fa7594d"}, + {file = "qiskit_terra-0.45.1-cp38-abi3-win32.whl", hash = "sha256:f9c641c6a14b599aca1de59c9c2c2cd7a711000749523f0d6f503a3445ddd4b6"}, + {file = "qiskit_terra-0.45.1-cp38-abi3-win_amd64.whl", hash = "sha256:f41387fa19145f5ed22fa5f7e3bed44ba4ee4ba06bfbfada83b5a516587f2a74"}, ] [package.dependencies] dill = ">=0.3" -numpy = ">=1.17" +numpy = ">=1.17,<2" ply = ">=3.10" psutil = ">=5" python-dateutil = ">=2.8.0" rustworkx = ">=0.13.0" scipy = ">=1.5" stevedore = ">=3.0.0" -symengine = {version = ">=0.9,<0.10", markers = "platform_machine == \"x86_64\" or platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"amd64\" or platform_machine == \"arm64\""} +symengine = {version = ">=0.9,<0.10.0 || >0.10.0", markers = "platform_machine == \"x86_64\" or platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"amd64\" or platform_machine == \"arm64\""} sympy = ">=1.3" typing-extensions = {version = "*", markers = "python_version < \"3.11\""} [package.extras] all = ["ipywidgets (>=7.3.0)", "matplotlib (>=3.3)", "pillow (>=4.2.1)", "pydot", "pygments (>=2.4)", "pylatexenc (>=1.4)", "python-constraint (>=1.4)", "qiskit-qasm3-import (>=0.1.0)", "seaborn (>=0.9.0)", "z3-solver (>=4.7)"] -bip-mapper = ["cplex", "docplex"] crosstalk-pass = ["z3-solver (>=4.7)"] csp-layout-pass = ["python-constraint (>=1.4)"] qasm3-import = ["qiskit-qasm3-import (>=0.1.0)"] -toqm = ["qiskit-toqm (>=0.1.0)"] visualization = ["ipywidgets (>=7.3.0)", "matplotlib (>=3.3)", "pillow (>=4.2.1)", "pydot", "pygments (>=2.4)", "pylatexenc (>=1.4)", "seaborn (>=0.9.0)"] [[package]] @@ -3497,14 +3496,14 @@ develop = false [package.dependencies] mplhep = "^0.3.30" -qiskit = "^0.44.2" +qiskit = "^0.45.0" quantum-linear-solvers = "^0.1.1" [package.source] type = "git" url = "https://github.com/dmark04/TrackHHL" reference = "HEAD" -resolved_reference = "ddc46306c7f8669d8cafd2037bd6eb91779caa3d" +resolved_reference = "331cdbfeaf943eb1119e8ead521839299b7749b7" [[package]] name = "traitlets" @@ -3775,4 +3774,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.9, <3.12" -content-hash = "611cb93a80146123e949a572673240d1304ccaecf8d05db7f32e353115efc638" +content-hash = "f6748ee5e5e3aa3d5bc84cc939749011b965ff83335f418a3d4cebcafc6d53c3" diff --git a/pyproject.toml b/pyproject.toml index 3fa19ba..a232c51 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ include = ["CHANGELOG.md"] [tool.poetry.dependencies] python = ">=3.9, <3.12" # >=3.9 because of amazon-braket, <3.12 because of classiq classiq = ">=0.32,<0.34" -qiskit = ">=0.43.0, <0.45.0" +qiskit = "^0.45.0" qiskit-algorithms = ">=0.2.1, <0.3.0" matplotlib = "^3.8.2" # this version could be lowered to 3.5.0 or even further back numpy = ">=1.23.0" diff --git a/quantum_linear_systems/execution/naive_vqls_hybrid_task.py b/quantum_linear_systems/execution/naive_vqls_hybrid_task.py index 05d281a..b549207 100644 --- a/quantum_linear_systems/execution/naive_vqls_hybrid_task.py +++ b/quantum_linear_systems/execution/naive_vqls_hybrid_task.py @@ -4,15 +4,36 @@ The results are then given to the classical part of the algorithm. This is looped over in a for loop. """ +import time +from typing import Any +from typing import Callable +from typing import List +from typing import Tuple +from typing import Union + import numpy as np +from qiskit import execute from qiskit import QuantumCircuit +from qiskit.algorithms.minimum_eigen_solvers.vqe import _validate_bounds +from qiskit.algorithms.minimum_eigen_solvers.vqe import _validate_initial_point +from qiskit.algorithms.optimizers import Minimizer +from qiskit.algorithms.optimizers import Optimizer from qiskit.circuit.library.n_local.real_amplitudes import RealAmplitudes from qiskit.primitives import Estimator from qiskit.primitives import Sampler +from qiskit.quantum_info import Statevector +from qiskit.result import QuasiDistribution +from qiskit_aer import QasmSimulator from qiskit_algorithms.optimizers import COBYLA from qiskit_algorithms.optimizers import SLSQP from vqls_prototype import VQLS -from vqls_prototype import VQLSLog + +from quantum_linear_systems.implementations.vqls_qiskit_implementation import ( + postprocess_solution, +) +from quantum_linear_systems.plotting import print_results +from quantum_linear_systems.toymodels import ClassiqDemoExample +from quantum_linear_systems.utils import circuit_to_qasm3 def naive_hybrid_solve_vqls( @@ -23,7 +44,8 @@ def naive_hybrid_solve_vqls( optimizer_name: str = "cobyla", optimizer_max_iter: int = 250, show_circuit: bool = False, -) -> None: +) -> Tuple[np.ndarray, str, int, int, float]: + start_time = time.time() np.set_printoptions(precision=3, suppress=True) if ansatz is None: @@ -34,23 +56,261 @@ def naive_hybrid_solve_vqls( insert_barriers=False, ) if optimizer_name.lower() == "cobyla": - optimizer = COBYLA(maxiter=optimizer_max_iter, disp=True) + optimizer = COBYLA(maxiter=1, disp=True) elif optimizer_name.lower() == "slsqp": - optimizer = SLSQP(maxiter=optimizer_max_iter, disp=True) + optimizer = SLSQP(maxiter=1, disp=True) else: raise ValueError(f"Invalid optimizer_name: {optimizer_name}") if vector_b.ndim == 2: vector_b = vector_b.flatten() - log = VQLSLog([], []) vqls = VQLS( estimator=estimator, ansatz=ansatz, optimizer=optimizer, sampler=Sampler(), - callback=log.update, + options={ + "use_overlap_test": False, + "use_local_cost_function": False, + "verbose": True, + }, + ) + # Note: copied from vqls._solve() + # compute the circuits needed for the hadamard tests + hdmr_tests_norm, hdmr_tests_overlap = vqls.construct_circuit(matrix_a, vector_b) + hdmr_norm_circuits = [c for h in hdmr_tests_norm for c in h.circuits] + hdmr_overlap_circuits = [c for h in hdmr_tests_overlap for c in h.circuits] + print("obserables", vqls.estimator.observables) + + # compute the coefficient matrix + coefficient_matrix = vqls.get_coefficient_matrix( + np.array([mat_i.coeff for mat_i in vqls.matrix_circuits]) + ) + + # set an expectation for this algorithm run (will be reset to None at the end) + # initial_point = _validate_initial_point(vqls.initial_point, vqls.ansatz) + bounds = _validate_bounds(vqls.ansatz) + + # Convert the gradient operator into a callable function that is compatible with the + # optimization routine. + gradient = vqls._gradient + # vqls._eval_count = 0 + + # get the cost evaluation function + # cost_evaluation = vqls.get_cost_evaluation_function( + # hdmr_tests_norm, hdmr_tests_overlap, coefficient_matrix + # ) + + # Step 1: determine initial set of parameters + parameter_names = ansatz.parameters + + initial_parameters: List[float] = _validate_initial_point( + vqls.initial_point, vqls.ansatz + ) + # Step 2: get circuits from estimator and assign initial parameters + + for n in range(optimizer_max_iter): + new_params, current_cost = naive_hybrid_loop( + norm_circuits=hdmr_norm_circuits, + overlap_circuits=hdmr_overlap_circuits, + parameter_names=parameter_names, + parameter_values=initial_parameters, + optimizer=vqls.optimizer, + gradient=gradient, + bounds=bounds, + coeff_matrix=coefficient_matrix, + vqls_instance=vqls, + ) + initial_parameters = new_params + print(f"Cost={current_cost} in iteration {n}/{optimizer_max_iter}.") + + # Step 6: create output state from final set of params + vqls_circuit = ansatz.assign_parameters( + { + param: value + for param, value in zip( + parameter_names, + initial_parameters, + ) + } + ) + raise NotImplementedError("Step 6: Not implemented yet.") + + vqls_solution_vector = np.real(Statevector(vqls_circuit).data) + + quantum_solution = postprocess_solution( + matrix_a=matrix_a, vector_b=vector_b, solution_x=vqls_solution_vector + ) + + qc_basis = vqls_circuit.decompose(reps=10) + + if show_circuit: + print(qc_basis) + + # todo: fix, make sure this is the right circuit + qasm_content = circuit_to_qasm3( + circuit=vqls_circuit, filename="vqls_qiskit_circuit.qasm3" + ) + + print( + f"Comparing depths original {vqls_circuit.depth()} vs. decomposed {qc_basis.depth()}" + ) + + return ( + quantum_solution, + qasm_content, + qc_basis.depth(), + vqls_circuit.width(), + time.time() - start_time, + ) + + +def naive_hybrid_loop( + norm_circuits: List[QuantumCircuit], + overlap_circuits: List[QuantumCircuit], + parameter_names: List[str], + parameter_values: List[float], + optimizer: Union[Optimizer, Minimizer], + gradient: Callable[[Any], Any], + bounds: list[tuple[float, float]], + coeff_matrix: np.ndarray, # also for evaluation of cost function + vqls_instance: VQLS, # so we don't have to copy everything for the cost function +) -> Tuple[List[float], float]: + norm_qasm = [ + circ.assign_parameters( + {param: value for param, value in zip(parameter_names, parameter_values)} + ).qasm() + for i, circ in enumerate(norm_circuits) + ] + + overlap_qasm = [ + circ.assign_parameters( + {param: value for param, value in zip(parameter_names, parameter_values)} + ).qasm() + for i, circ in enumerate(overlap_circuits) + ] + qasm_circuits = (norm_qasm, overlap_qasm) + + # Step 3: Execute on quantum backend + def execute_quantum( + qasm_circs: Tuple[List[str], List[str]], + ) -> Tuple[List[float], List[float]]: + """Execute the different circuits on the quantum device.""" + # todo: for QuantumInspire this needs to be replaced with what they do, here a qiskit implementation: + backend = QasmSimulator() + norm_qasm, overlap_qasm = qasm_circs + # todo: here we need to figure out how we extract the desired quantity from the results + norm_results = [] + for qcirc in norm_qasm: + qc = QuantumCircuit.from_qasm_str(qcirc) + # Add measurements to the circuit if not already present + qc.measure_all() + job = execute(qc, backend) + result = job.result().get_counts() + quasi_dist = QuasiDistribution(result) + norm_results.append(quasi_dist) + overlap_results = [] + for qcirc in overlap_qasm: + qc = QuantumCircuit.from_qasm_str(qcirc) + # Add measurements to the circuit if not already present + qc.measure_all() + job = execute(qc, backend) + result = job.result().get_counts() + quasi_dist = QuasiDistribution(result) + overlap_results.append(quasi_dist) + print(norm_results) + norm_results = test_post_processing( + norm_results, + num_qubits=vqls_instance._get_local_circuits()[0].num_qubits, + post_process_coeffs=vqls_instance._get_local_circuits()[ + 0 + ].post_process_coeffs, + ) + norm_results = np.array([1.0 - 2.0 * val for val in norm_results]).astype( + "complex128" + ) + norm_results *= np.array([1.0, 1.0j]) + print(norm_results) + exit() + return norm_results, overlap_results + + norm_res, overlap_res = execute_quantum(qasm_circs=qasm_circuits) + + # Step 4: minimize cost function + def cost_function(x0: List[float]) -> float: + """VQLS cost function.""" + # Note since we already evaluated the circuits the input params are not used here + # Note: copied from vqls + cost_value = vqls_instance._assemble_cost_function( + hdmr_values_norm=norm_res, + hdmr_values_overlap=overlap_res, + coefficient_matrix=coeff_matrix, + ) + return float(cost_value) + + results = optimizer.minimize( + fun=cost_function, + x0=parameter_values, + jac=gradient, + bounds=bounds, + ) + + # Step 5: get new parameters to start + new_params = results.x + cost = results.fun + + return new_params, cost + + +def test_post_processing( # type: ignore + sampler_result, num_qubits: int, post_process_coeffs +) -> np.ndarray: + """Post process the sampled values of the circuits. + + Args: + sampler_result (results): Result of the sampler + + Returns: + List: value of the overlap hadammard test + """ + + # quasi_dist = sampler_result.quasi_dists + quasi_dist = sampler_result + output = [] + + for qdist in quasi_dist: + # add missing keys + val = np.array([qdist[k] if k in qdist else 0 for k in range(2**num_qubits)]) + + value_0, value_1 = val[0::2], val[1::2] + proba_0 = (value_0 * post_process_coeffs).sum() + proba_1 = (value_1 * post_process_coeffs).sum() + + output.append(proba_0 - proba_1) + + return np.array(output).astype("complex128") + + +if __name__ == "__main__": + N = 1 + + model = ClassiqDemoExample() + # model = HEPTrackReconstruction(num_detectors=5, num_particles=5) + # runtimes(250): 3,3 =150s; 4,3=153s; 4,4=677s ;5,4=654s (c.25) ; 5,5=3492s (c0.34) + # Note: neither memory nor cpu usage significant at these sizes + # Note: after 250 iterations the cost is not low enough, would it make more sense to define different stop criteria + qsol, _, depth, width, run_time = naive_hybrid_solve_vqls( + matrix_a=model.matrix_a, + vector_b=model.vector_b, + show_circuit=True, + optimizer_max_iter=1, + ) + + print_results( + quantum_solution=qsol, + classical_solution=model.classical_solution, + run_time=run_time, + name=model.name, + plot=False, ) - opt = {"use_overlap_test": False, "use_local_cost_function": False} - # todo: here we diverge and write our own function for solve, where we split quantum circuit and optimization - vqls.solve(matrix_a, vector_b, opt) diff --git a/quantum_linear_systems/execution/quantum_inspire_backend.py b/quantum_linear_systems/execution/quantum_inspire_backend.py new file mode 100644 index 0000000..d7d74bf --- /dev/null +++ b/quantum_linear_systems/execution/quantum_inspire_backend.py @@ -0,0 +1,105 @@ +import datetime +from typing import Any +from typing import Dict +from typing import List +from typing import Optional + +from qiskit.providers.backend import BackendV2 +from qiskit.providers.provider import ProviderV1 + + +class QuTechProvider(ProviderV1): + """Provider class for QuTech.""" + + def __init__(self) -> None: + self._backends = [QuantumInspireBackend] + + def backends( + self, name: Optional[str] = None, **kwargs: Dict[Any, Any] + ) -> List[BackendV2]: + """Return a list of backends matching the specified filtering. + + Args: + name (str): name of the backend. + **kwargs: dict used for filtering. + + Returns: + list[Backend]: a list of Backends that match the filtering + criteria. + """ + if name in self._backends and name is not None: # type: ignore + return [backend for backend in self._backends if backend.name == name] + else: + raise ValueError(f"Backend {name} not found for provider QuTech") + + +class QuantumInspireBackend(BackendV2): + """Backend class for QuantumInspire.""" + + def __init__(self) -> None: + super().__init__( + provider=QuTechProvider(), + name="quantum_inspire", + description="QuantumInspire backend.", + online_date=datetime.datetime, + backend_version="1.0", + ) + + def target(self) -> None: + """A :class:`qiskit.transpiler.Target` object for the backend. + + :rtype: Target + """ + pass + + def max_circuits(self) -> None: + """The maximum number of circuits (or Pulse schedules) that can be run in a + single job. + + If there is no limit this will return None + """ + pass + + @classmethod + def _default_options(cls) -> None: + """Return the default options. + + This method will return a :class:`qiskit.providers.Options` + subclass object that will be used for the default options. These + should be the default parameters to use for the options of the + backend. + + Returns: + qiskit.providers.Options: A options object with + default values set + """ + pass + + @property + def dtm(self) -> float: + """Return the system time resolution of output signals. + + Returns: + The output signal timestep in seconds. + + Raises: + NotImplementedError: if the backend doesn't support querying the + output signal timestep + """ + raise NotImplementedError + + @property + def meas_map(self) -> List[List[int]]: + """Return the grouping of measurements which are multiplexed. + + This is required to be implemented if the backend supports Pulse + scheduling. + + Returns: + The grouping of measurements which are multiplexed + + Raises: + NotImplementedError: if the backend doesn't support querying the + measurement mapping + """ + raise NotImplementedError diff --git a/quantum_linear_systems/implementations/vqls_qiskit_implementation.py b/quantum_linear_systems/implementations/vqls_qiskit_implementation.py index 2baf3d1..e5a3a8b 100644 --- a/quantum_linear_systems/implementations/vqls_qiskit_implementation.py +++ b/quantum_linear_systems/implementations/vqls_qiskit_implementation.py @@ -56,7 +56,11 @@ def solve_vqls_qiskit( ansatz=ansatz, optimizer=optimizer, sampler=Sampler(), - options={"use_overlap_test": False, "use_local_cost_function": False}, + options={ + "use_overlap_test": False, + "use_local_cost_function": False, + "verbose": True, + }, ) res = vqls.solve(matrix_a, vector_b)