diff --git a/.github/workflows/ci_basic.yml b/.github/workflows/ci_basic.yml index b6bd5930..ae797b0a 100644 --- a/.github/workflows/ci_basic.yml +++ b/.github/workflows/ci_basic.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.9', '3.10'] + python-version: ['3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v2 @@ -30,6 +30,7 @@ jobs: pip install -r requirements.txt # test default simulator against in-house simulator pip install git+https://github.com/Mikel-Ma/spex@devel + pip install excitationsolve - name: Lint with flake8 run: | pip install flake8 diff --git a/.github/workflows/ci_basic_autograd.yml b/.github/workflows/ci_basic_autograd.yml index 3ca8463d..a71d09d0 100644 --- a/.github/workflows/ci_basic_autograd.yml +++ b/.github/workflows/ci_basic_autograd.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.9', '3.10'] + python-version: ['3.11'] steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/ci_chemistry_psi4.yml b/.github/workflows/ci_chemistry_psi4.yml index 3334474b..f43bacd9 100644 --- a/.github/workflows/ci_chemistry_psi4.yml +++ b/.github/workflows/ci_chemistry_psi4.yml @@ -22,7 +22,7 @@ jobs: with: miniconda-version: 'latest' auto-activate-base: false - python-version: '3.10' + python-version: '3.11' - name: Initialize Conda for the shell run: | diff --git a/.github/workflows/ci_chemistry_pyscf.yml b/.github/workflows/ci_chemistry_pyscf.yml index f68e6c64..97a4ea8b 100644 --- a/.github/workflows/ci_chemistry_pyscf.yml +++ b/.github/workflows/ci_chemistry_pyscf.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9] + python-version: ['3.10'] steps: - uses: actions/checkout@v2 @@ -39,7 +39,7 @@ jobs: python -m pip install -r requirements.txt python -m pip install -e . python -m pip install pyscf - python -m pip install 'h5py <= 3.1' + #python -m pip install 'h5py <= 3.1' cd tests ls pytest test_chemistry.py test_TrotErr.py -m "not dependencies" diff --git a/.github/workflows/ci_conda_madness.yml b/.github/workflows/ci_conda_madness.yml index ed042d4a..abc289b7 100644 --- a/.github/workflows/ci_conda_madness.yml +++ b/.github/workflows/ci_conda_madness.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9] + python-version: [3.11] steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/ci_ml.yml b/.github/workflows/ci_ml.yml index e649b377..77574d92 100644 --- a/.github/workflows/ci_ml.yml +++ b/.github/workflows/ci_ml.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9] + python-version: [3.11] steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/ci_pyquil.yml b/.github/workflows/ci_pyquil.yml deleted file mode 100644 index 926ec8dc..00000000 --- a/.github/workflows/ci_pyquil.yml +++ /dev/null @@ -1,45 +0,0 @@ -# This workflow will install Python dependencies, run tests and lint with a variety of Python versions -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions - -name: Tequila-Test-Pyquil - -on: - push: - branches: [ master, devel ] - pull_request: - branches: [] - -jobs: - - build: - - runs-on: ubuntu-latest - strategy: - matrix: - python-version: [3.9] - - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 - with: - python-version: ${{ matrix.python-version }} - - name: Install and run - run: | - python -m pip install --upgrade pip - pip install --upgrade pytest - pip install -r requirements.txt - pip install "pyquil<3.0" # needs updates - pip install -e . - pip install --upgrade pip 'urllib3<2' # issues with old pyquil version otherwise - docker pull rigetti/qvm:edge - docker pull rigetti/quilc - docker run --rm -itd -p 5555:5555 rigetti/quilc -R - docker run --rm -itd -p 5000:5000 rigetti/qvm -S - pytest -m "not dependencies" tests/test_simulator_backends.py --slow - pytest tests/test_recompilation_routines.py --slow - pytest -m "not dependencies" tests/test_noise.py --slow - pytest tests/test_gradient.py --slow - pytest tests/test_scipy.py --slow - pytest tests/test_mappings.py --slow - diff --git a/README.md b/README.md index 233e282b..701a4eb7 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,8 @@ Tequila can execute the underlying quantum expectation values on state of the ar # Getting Started Get started with our collection of -- *[Tutorials](https://tequilahub.github.io/tequila-tutorials/)* +- *[Tutorials](https://tequilahub.github.io/tequila-tutorials/)* +- *[Documentation](https://tequilahub.github.io/tequila-tutorials/docs/sphinx/)* Further sources: - [overview article](https://arxiv.org/abs/2011.03057) @@ -17,7 +18,7 @@ Further sources: - [talks and slides](https://kottmanj.github.io/talks_and_material/) # Installation -Recommended Python version is 3.10 (3.11). +Recommended Python version is 3.11 (3.10 or 3.12 shouldn't be a problem either). Tequila supports linux, osx and windows. However, not all optional dependencies (especially chemistry) are supported on windows. ## Install from PyPi @@ -295,12 +296,7 @@ pip install pyscf Works similar as Psi4. Classical methods are also integrated in the madness interface allowing to use them in a basis-set-free representation. # Documentation -You can build the documentation by navigating to `docs` and entering `make html`. -Open the documentation with a browser over like `firefox docs/build/html/index.html` -Note that you will need some additional python packages like `sphinx` and `mr2` that are not explicitly listed in the requirements.txt - -You can also visit our prebuild online [documentation](https://tequilahub.github.io/tequila/) -that will correspond to the github master branch +see [here](https://tequilahub.github.io/tequila-tutorials/docs/sphinx/) # How to contribute If you find any bugs or inconveniences in `tequila` please don't be shy and let us know. diff --git a/requirements.txt b/requirements.txt index 68c0382b..e0a81911 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,6 +10,7 @@ pytest openfermion ~= 1.0 # can not be smaller than 1.0 #cmake # needed by qulacs, can be removed otherwise, now in qulacs requirements qulacs # default simulator (best integration), remove if the installation gives you trouble and just install one of the other supported backend. Version restriction only for noise models, otherwise the new version is fine +excitationsolve #optional quantum backends #cirq >= 0.9.2 # diff --git a/src/tequila/grouping/fermionic_functions.py b/src/tequila/grouping/fermionic_functions.py index 4a7bc5d4..1454bdb9 100644 --- a/src/tequila/grouping/fermionic_functions.py +++ b/src/tequila/grouping/fermionic_functions.py @@ -589,8 +589,12 @@ def find_index(basis_state): index = 0 n_qubits = len(basis_state) for j in range(n_qubits): - index += int(basis_state[j]) * 2 ** (n_qubits - j - 1) - + try: + index += int(basis_state[j]) * 2 ** (n_qubits - j - 1) + except Exception as E: + print("basis_state=", basis_state) + print(f"j={j}") + print(f"basis_state[j]={basis_state[j]}") return index diff --git a/src/tequila/optimizers/__init__.py b/src/tequila/optimizers/__init__.py index 206c6d65..14f47d44 100644 --- a/src/tequila/optimizers/__init__.py +++ b/src/tequila/optimizers/__init__.py @@ -1,8 +1,10 @@ from tequila.optimizers.optimizer_base import OptimizerHistory, Optimizer, TequilaOptimizerException, OptimizerResults from tequila.optimizers.optimizer_scipy import OptimizerSciPy from tequila.optimizers.optimizer_gd import OptimizerGD +from tequila.optimizers.optimizer_excsolve import OptimizerExcitationSolve from tequila.optimizers.optimizer_scipy import minimize as minimize_scipy from tequila.optimizers.optimizer_gd import minimize as minimize_gd +from tequila.optimizers.optimizer_excsolve import minimize as minimize_excsolve from tequila.simulators.simulator_api import simulate from dataclasses import dataclass @@ -18,13 +20,14 @@ class _Optimizers: methods: list = None -SUPPORTED_OPTIMIZERS = ["scipy", "gpyopt", "gd"] +SUPPORTED_OPTIMIZERS = ["scipy", "gpyopt", "gd", "excitationsolve"] INSTALLED_OPTIMIZERS = {} INSTALLED_OPTIMIZERS["scipy"] = _Optimizers( cls=OptimizerSciPy, minimize=minimize_scipy, methods=OptimizerSciPy.available_methods() ) INSTALLED_OPTIMIZERS["gd"] = _Optimizers(cls=OptimizerGD, minimize=minimize_gd, methods=OptimizerGD.available_methods()) + has_gpyopt = False try: from tequila.optimizers.optimizer_gpyopt import OptimizerGPyOpt @@ -37,6 +40,18 @@ class _Optimizers: except ImportError: has_gpyopt = False +has_excsolve = False +try: + from tequila.optimizers.optimizer_excsolve import OptimizerExcitationSolve + from tequila.optimizers.optimizer_excsolve import minimize as minimize_excsolve + + INSTALLED_OPTIMIZERS["excitationsolve"] = _Optimizers( + cls=OptimizerExcitationSolve, minimize=minimize_excsolve, methods=OptimizerExcitationSolve.available_methods() + ) + has_excsolve = True +except ImportError: + has_excsolve = False + def show_available_optimizers(module=None): """ @@ -138,5 +153,5 @@ def minimize( ) raise TequilaOptimizerException( - "Could not find optimization method {} in tequila optimizers. You might miss dependencies" + f"Could not find optimization method {method} in tequila optimizers. You might miss dependencies" ) diff --git a/src/tequila/optimizers/optimizer_excsolve.py b/src/tequila/optimizers/optimizer_excsolve.py new file mode 100644 index 00000000..ae51e4f7 --- /dev/null +++ b/src/tequila/optimizers/optimizer_excsolve.py @@ -0,0 +1,142 @@ +import scipy +import numpy +import typing +import numbers +from excitationsolve import ExcitationSolveScipy +from tequila.objective import Objective +from tequila.objective.objective import assign_variable, Variable, format_variable_dictionary, format_variable_list +from .optimizer_base import Optimizer, OptimizerResults +from ._containers import _EvalContainer, _GradContainer, _HessContainer, _QngContainer +from .optimizer_scipy import SciPyResults +from tequila.utils.exceptions import TequilaException +from tequila.circuit.noise import NoiseModel +from tequila.tools.qng import get_qng_combos + +from dataclasses import dataclass + + +class OptimizerExcitationSolve(Optimizer): + r"""The ExcitationSolve optimizer as a SciPy optimizer that can be given to the scipy.optimize.minimize function. + + Usage: + ```python + excsolve_obj = ExcitationSolveScipy(maxiter=100, tol=1e-10, save_parameters=True) + optimizer = excsolve_obj.minimize + res = scipy.optimize.minimize(cost, params, method=optimizer) + energies = excsolve_obj.energies + counts = excsolve_obj.nfevs + ``` + + Note that this optimizer never needs to evaluate the ansatz circuit + at the (current) optimal parameters, unless the optimal parameters fall onto + the sample points used to reconstruct the energy function. + Therefore, when used with a qiskit VQE object, the energies transmitted + to a VQE callback function, do not seem to improve or converge. Nevertheless, + the determined optimal energy and parameters are still returned. + + Args: + maxiter (int): Maximum number of VQE iterations (maximum number of times to optimize all parameters) + tol: Threshold of energy difference after subsequent VQE iterations defining convergence + num_samples (int, optional): Number of different parameter values at which to sample + the energy to reconstruct the energy function in one parameter. + Must be greater or equal to 5. Defaults to 5. + hf_energy (float | None, optional): The Hartree-Fock energy, i.e. the energy of the + system where all parameters in the circuit are zero. If none, this will be + calculated by evaluating the energy of the ansatz with all parameters set to zero. + If this energy is known from a prior classical calculation, e.g. a Hartree-Fock + calculation, one energy evaluation is saved. Defaults to None. + save_parameters (bool, optional): If True, params member variable contains + all optimal parameter values after each optimization step, + i.e. after optimizing each single parameter. Defaults to False. + param_scaling (float, optional): Factor used for rescaling the parameters. This ExcitationSolve optimizer + expects the parameters to be 2\pi periodic. For example, in Qiskit + the excitation parameters result in excitation operators being \pi periodic. + Therefore, we use a factor of 0.5 for qiskit, resulting in a Period of 2\pi. + """ + + @classmethod + def available_methods(cls): + """:return: All tested available methods""" + return ["excitationsolve"] + + def __init__( + self, maxiter, tol=1e-12, num_samples=5, hf_energy=None, save_parameters=False, param_scaling=0.5, **kwargs + ): + if maxiter is None: + maxiter = 10 + + super().__init__(**kwargs) + + self.opt = ExcitationSolveScipy( + maxiter=maxiter, + tol=tol, + num_samples=num_samples, + hf_energy=hf_energy, + save_parameters=save_parameters, + param_scaling=param_scaling, + ) + + def __call__( + self, + objective: Objective, + variables: typing.List[Variable], + initial_values: typing.Dict[Variable, numbers.Real] = None, + *args, + **kwargs, + ) -> SciPyResults: + objective = objective.contract() + infostring = "{:15} : {}\n".format("Method", "ExcitationSolve") + infostring += "{:15} : {} expectationvalues\n".format("Objective", objective.count_expectationvalues()) + + # if self.save_history and reset_history: + # self.reset_history() + + active_angles, passive_angles, variables = self.initialize_variables(objective, initial_values, variables) + + # Transform the initial value directory into (ordered) arrays + param_keys, param_values = zip(*active_angles.items()) + param_values = numpy.array(param_values) + + # do the compilation here to avoid costly recompilation during the optimization + compiled_objective = self.compile_objective(objective=objective, *args, **kwargs) + E = _EvalContainer( + objective=compiled_objective, + param_keys=param_keys, + samples=self.samples, + passive_angles=passive_angles, + save_history=self.save_history, + print_level=self.print_level, + ) + + res = self.opt.minimize(E, param_values, *args, **kwargs) + + if self.save_history: + self.history.energies = self.opt.energies + self.history.angles = self.opt.params + # self.history.gradients = self.opt.energies_shiftste + + return SciPyResults(energy=res.fun, history=self.history, variables=res.x, scipy_result=res) + + +def minimize( + objective: Objective, + variables: typing.List[Variable], + initial_values: typing.Dict[Variable, numbers.Real] = None, + method: str = "excitationsolve", + maxiter: int = 10, + *args, + **kwargs, +): + optimize = OptimizerExcitationSolve( + maxiter=maxiter, + save_parameters=True, + *args, + **kwargs, + ) + return optimize( + objective=objective, + variables=variables, + initial_values=initial_values, + *args, + **kwargs, + ) diff --git a/src/tequila/version.py b/src/tequila/version.py index efefaf49..49325def 100644 --- a/src/tequila/version.py +++ b/src/tequila/version.py @@ -1,2 +1,2 @@ -__version__ = "1.9.10.dev" +__version__ = "1.9.10" __author__ = "Tequila Developers " diff --git a/tests/test_chemistry.py b/tests/test_chemistry.py index b4b68aeb..b8be2318 100644 --- a/tests/test_chemistry.py +++ b/tests/test_chemistry.py @@ -16,11 +16,13 @@ # Get QC backends for parametrized testing import select_backends - HAS_PYSCF = "pyscf" in qc.INSTALLED_QCHEMISTRY_BACKENDS HAS_PSI4 = "psi4" in qc.INSTALLED_QCHEMISTRY_BACKENDS backends = select_backends.get() +standard_trafos = ["JordanWigner", "ReorderedJordanWigner", "BravyiKitaev"] +trafos = standard_trafos +# trafos = list(known_encodings().keys() # currently issues with openfermion def teardown_function(function): @@ -48,7 +50,7 @@ def test_UR_and_UC(): assert numpy.isclose(result.energy, fci) -@pytest.mark.parametrize("trafo", list(known_encodings().keys())) +@pytest.mark.parametrize("trafo", trafos) def test_base(trafo): obt = numpy.asarray([[-1.94102524, -0.31651552], [-0.31651552, -0.0887454]]) tbt = numpy.asarray( @@ -79,18 +81,13 @@ def test_base(trafo): H = molecule.make_hamiltonian() eigvals = numpy.linalg.eigvalsh(H.to_matrix()) assert numpy.isclose(eigvals[0], -2.87016214e00) - if "trafo" in [ - "JordanWigner", - "BravyiKitaev", - "bravyi_kitaev_fast", - "BravyiKitaevTree", - ]: # others change spectrum outside of the groundstate + if "trafo" in standard_trafos: # others change spectrum outside of the groundstate assert numpy.isclose(eigvals[-1], 7.10921141e-01) assert len(eigvals) == 16 @pytest.mark.skipif(condition=not HAS_PSI4 and not HAS_PYSCF, reason="you don't have psi4 or pyscf") -@pytest.mark.parametrize("trafo", ["JordanWigner", "BravyiKitaev", "BravyiKitaevTree", "TaperedBinary"]) +@pytest.mark.parametrize("trafo", trafos) def test_prepare_reference(trafo): geometry = "Li 0.0 0.0 0.0\nH 0.0 0.0 1.5" basis_set = "sto-3g" @@ -101,7 +98,7 @@ def test_prepare_reference(trafo): energy = tq.simulate(E) hf_energy = mol.compute_energy("hf") assert numpy.isclose(energy, hf_energy, atol=1.0e-4) - mol = tq.Molecule(geometry=geometry, units="angstrom", basis_set=basis_set, transformation="reordered" + trafo) + mol = tq.Molecule(geometry=geometry, units="angstrom", basis_set=basis_set, transformation=trafo) H = mol.make_hamiltonian() U = mol.prepare_reference() E = tq.ExpectationValue(H=H, U=U) @@ -190,9 +187,7 @@ def do_test_h2_hamiltonian(qc_interface): @pytest.mark.skipif(condition=not HAS_PSI4, reason="you don't have psi4") -@pytest.mark.parametrize( - "trafo", ["JordanWigner", "BravyiKitaev", "BravyiKitaevTree", "TaperedBinary"] -) # bravyi_kitaev_fast not yet supported for ucc +@pytest.mark.parametrize("trafo", trafos) @pytest.mark.parametrize("backend", backends) def test_ucc_psi4(trafo, backend): if backend == "symbolic": @@ -378,7 +373,7 @@ def test_rdms_psi4(): @pytest.mark.skipif(condition=not HAS_PSI4 and not HAS_PYSCF, reason="psi4/pyscf not found") @pytest.mark.parametrize("geometry", ["H 0.0 0.0 0.0\nH 0.0 0.0 0.7"]) -@pytest.mark.parametrize("trafo", tq.quantumchemistry.encodings.known_encodings()) +@pytest.mark.parametrize("trafo", trafos) def test_upccgsd(geometry, trafo): molecule = tq.chemistry.Molecule(geometry=geometry, units="angstrom", basis_set="sto-3g", transformation=trafo) if not molecule.supports_ucc(): @@ -490,18 +485,7 @@ def test_fermionic_gates(assume_real, trafo): @pytest.mark.skipif(condition=not HAS_PSI4 and not HAS_PYSCF, reason="psi4/pyscf not found") -@pytest.mark.parametrize( - "trafo", - [ - "JordanWigner", - "BravyiKitaev", - "BravyiKitaevTree", - "ReorderedJordanWigner", - "ReorderedBravyiKitaev", - "TaperedBinary", - "REORDEREDTAPEREDBINARY", - ], -) +@pytest.mark.parametrize("trafo", trafos) def test_hcb(trafo): geomstring = "Be 0.0 0.0 0.0\n H 0.0 0.0 1.6\n H 0.0 0.0 -1.6" mol1 = tq.Molecule( @@ -786,7 +770,7 @@ def test_spa_ansatz_be(): "geometry", ["H 0.0 0.0 0.0\nH 0.0 0.0 4.5", "Li 0.0 0.0 0.0\nH 0.0 0.0 3.0", "Be 0.0 0.0 0.0\nH 0.0 0.0 3.0\nH 0.0 0.0 -3.0"], ) -@pytest.mark.parametrize("transformation", tq.quantumchemistry.encodings.known_encodings()) +@pytest.mark.parametrize("transformation", trafos) @pytest.mark.skipif(condition=not HAS_PSI4 and not HAS_PYSCF, reason="psi4/pyscf not found") def test_spa_consistency(geometry, name, optimize, transformation): mol = tq.Molecule( @@ -942,17 +926,7 @@ def test_orbital_optimization_hcb(geometry): assert (numpy.isclose(opt1.mo_coeff, opt2.mo_coeff, atol=1.0e-5)).all() -@pytest.mark.parametrize( - "transformation", - [ - "JordanWigner", - "ReorderedJordanWigner", - "BravyiKitaev", - "BravyiKitaevTree", - "TaperedBinary", - "REORDEREDTAPEREDBINARY", - ], -) +@pytest.mark.parametrize("transformation", trafos) @pytest.mark.parametrize("size", [2, 8]) def test_givens_on_molecule(size, transformation): # dummy one-electron integrals