Skip to content

Commit

Permalink
Merge branch 'main' into accIQAE
Browse files Browse the repository at this point in the history
  • Loading branch information
renezander90 committed Nov 28, 2024
2 parents d31ce23 + 4867eb4 commit 182f975
Show file tree
Hide file tree
Showing 197 changed files with 11,893 additions and 3,997 deletions.
1 change: 1 addition & 0 deletions .github/workflows/qrisp_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ jobs:
pip install -e ./
pip install pennylane
pip install qiskit-aer
pip install pyscf
pip install qiskit-iqm
- name: Test with pytest
run: |
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Of course, Qrisp offers much more than just handling strings with a quantum comp


## Authors and Citation
Qrisp was mainly devised and implemented by Raphael Seidel, supported by Sebastian Bock and Nikolay Tcholtchev.
Qrisp was mainly devised and implemented by Raphael Seidel, supported by Sebastian Bock, Nikolay Tcholtchev, René Zander, Niklas Steinmann and Matic Petric.

If you have comments, questions or love letters, feel free to reach out to us:

Expand Down
Binary file added documentation/source/_static/BeH2_PEC_4_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added documentation/source/_static/BeH2_PEC_6_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added documentation/source/_static/H2_PEC.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added documentation/source/_static/hydrogen.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added documentation/source/_static/vqeH2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
58 changes: 58 additions & 0 deletions documentation/source/general/changelog/0.5.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
.. _v0.5:

Qrisp 0.5
=========

Welcome to the latest update of Qrisp, our quantum computing programming language! This release introduces powerful new features designed to enhance your quantum programming experience. We are excited to announce the addition of Hamiltonian simulations, enabling users to tackle complex chemistry problems with greater efficiency and precision. Additionally, a cutting-edge algorithm for combinatorial optimization has been integrated, offering significant improvements in solving challenging optimization tasks. Alongside these advancements, we've also introduced several new algorithmic primitives, further expanding the capabilities of Qrisp.

The Operators module
--------------------

This module allows you to describe operators that are not necessarily unitary or even hermitian. With these tools at hand you can effectively set up two of quantum's most important algorithms: :ref:`Hamiltonian simulation <ham_sim>` and the :ref:`VQE`.

For this purpose, Qrisp provides the following classes:

* :ref:`QubitOperator` is a class that enables the representation and processing of operators acting on a qubit space: $(\mathbb{C}^2)^{\otimes n}$. QubitOperators can be constructed based on a variety of tensor factors including the Pauli-matrices but also projector, creators and annihilators. For the latter Qrisp features a highly performant algorithm for Hamiltonian simulation proposed by `Kornell and Selinger <https://arxiv.org/abs/2310.12256>`_.
* :ref:`FermionicOperator` provides you with the tools to describe and manipulate fermionic systems. This type of system is of particular importance for applications in chemistry because many computations for molecular dynamics involve the simulation of electrons, which are fermions. FermionicOperators can be :ref:`constructed effortlessly <pscf_loading>` from `PySCF <https://pyscf.org/>`_ data. PySCF is one of the most popular quantum chemistry Python packages.
* :ref:`VQEProblem` which allows you to quickly formulate and run the VQE algorithm using Hamiltonians expressed through the two operator classes.

Explore these features in the following examples and tutorials: :ref:`H2` :ref:`computing the ground state energy of the Hydrogen molecule <GroundStateEnergyQPE>`, :ref:`determine the molecular potential energy curve of the Hydrogen molecule <MolecularPotentialEnergyCurve>`, :ref:`simulate the Ising-modell with a disturbance <IsingModel>`.

Hamiltonian simulation in Qrisp is very performant. In a benchmark against Google's `OpenFermion <https://quantumai.google/openfermion/tutorials/intro_workshop_exercises#hamiltonian_simulation_with_trotter_formulas>`_, Qrisp requires 20 times less RZ gates.


.. image:: openfermion_comparison.png
:width: 400px
:align: center

The amount of RZ gates is proportional to the required computation effort because Hamiltonian simulation is an algorithm, which requires fault tolerant devices. In this device model, arbitrary angle gates (like RZ, P or RX) need to be synthesized by a sequence of Clifford+T gates. More details `here <https://arxiv.org/abs/1403.2975>`__.


QIRO
----

The :ref:`QIRO algorithm <QIRO>` introduced by J. Finzgar et. al. in `Quantum-Informed Recursive Optimization Algorithms (2023) <https://arxiv.org/abs/2308.13607>`_ has been implemented.
The algorithm is based on updating the problem instance based on correlations, that are in turn established with a QAOA protocol. For further info have a look at our :ref:`tutorial on QIRO! <qiro_tutorial>`
The central data structure of the QIRO module is the :ref:`QIROProblem` class.

Compiler upgrades
-----------------

* A significantly faster algorithm for memory management has been implemented. With this feature, managing circuits with thousands of qubits is no problem.
* The compiler can now also leverage X-Permeability type commutativity relations. More info `here <https://quantum-compilers.github.io/iwqc2024/papers/IWQC2024_paper_16.pdf>`__.

Algorithmic primitives
----------------------

* :ref:`A module <phase_polynomials>` for the efficient treatment of phase polynomials has been implemented.
* :ref:`Quantum switch-case <qswitch>` can be used to execute a `switch statement <https://en.wikipedia.org/wiki/Switch_statement>`_ in superposition.
* Implemented a :ref:`Dicke state preparation algorithm <DickeStates>`.

Minor features
--------------

* It is now possible for backends to specify their own default shot count.
* Deprecated the QuantumNetworks module.
* :ref:`Operations <Operation>` can now receive complex numbers as parameters.
* :ref:`QuantumModulus` will now use the user-specified adder for all arithmetic evaluations (previously only in-place multiplication).
* A :ref:`tutorial <sudoku>` for utilizing the Quantum-Backtracking algorithm for solving Sudokus is now available.
1 change: 1 addition & 0 deletions documentation/source/general/changelog/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Version history
.. toctree::
:maxdepth: 2

0.5
0.4
0.3
0.2
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
260 changes: 260 additions & 0 deletions documentation/source/general/tutorial/H2.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
.. _H2:

Simulating the dynamics of the $H_2$ molecule
=============================================

Quantum computing offers a promising avenue for simulating complex molecular systems, potentially revolutionizing fields like drug discovery, materials science, and chemical engineering. At the heart of this capability lies Hamiltonian simulation, a powerful technique that allows us to model the quantum behavior of molecules with exponentially less resources compared to classical computers.
In this tutorial, we'll explore how to simulate molecules using Hamiltonian simulation techniques implemented in Qrisp. Since version 0.5, Qrisp provides a user-friendly interface for simulating quantum many body systems, making it a suitable tool for tackling complex quantum chemistry problems.
We'll cover the following key aspects:

* :ref:`The fundamentals of Hamiltonian simulation and the second quantization <ham_sim_fundamentals>`
* :ref:`Loading molecular data into Qrisp <molecular_data_tut>`
* :ref:`Performing Hamiltonian simulation <ham_sim_tut>`

By the end of this tutorial, you'll have a solid understanding of how to leverage Qrisp's capabilities to perform molecular simulations on quantum computers.
Let's dive in and discover how Qrisp can help us unlock the quantum secrets of molecules!

.. _ham_sim_fundamentals:

The fundamentals of Hamiltonian simulation and the second quantization
----------------------------------------------------------------------


To understand what "second quantization" means it might be helpful to start with the **first quantization**. The idea behind this is that physical systems containing only a single particale are described by a Hilbert space $\mathcal{H}$, which consits of a set of basis vectors:

.. math::
\mathcal{H} = \text{span}(\ket{\psi_i} \mid i \in \mathcal{N})
An arbitrary state $\ket{\phi}$ can therefore be represented by a linear combination of these basis vectors:

.. math::
\ket{\phi} = \sum_{i = 0}^\infty a_i \ket{\psi_i},\quad a_i \in \mathbb{C}
What these particular $\ket{\psi_i}$ represent depends on the system. For instance for the Hydrogen atom these basis vectors represent the `atomic orbitals <https://en.wikipedia.org/wiki/Atomic_orbital>`_. If you participated in a course for atomic physics before, you might have seen pictures like this:

.. image:: ./hydrogen_with_axes.png
:width: 400px
:align: center

This plot represents the real part of the amplitude of one of the basis vectors (the one with $n = 4, k = 3, l = 1$ to be precise).

The **second quantization** now describes how systems including *multiple particles* are treated. For this the so called ladder operators are introduced, that is, for each basis state $\ket{\psi_i}$ there is an operator $a_i^\dagger$, which creates a particle in state $\ket{\psi_i}$. A creative notation for this might be:

.. math::
a_i^\dagger \ket{\phi} = \ket{\phi \text{ + a particle in state i}}
For this reason, this operator is called the "creator". Similarly there is also the "annihilator" $a_i$, which destroys the particle.

The ladder operators therefore determine the intrinsic interaction behavior of the particles. Observations from experiments show that there are two types of particles whose ladder operators follow different sets of laws.

For **Bosons** the ladder operators obey `commutator <https://en.wikipedia.org/wiki/Commutator>`_ laws:

.. math::
[a_i, a^\dagger_j] = a_i a^\dagger_j - a_j a^\dagger_i = \delta_{ij}\\
[a^\dagger_i, a^\dagger_j] = [a_i, a_j] = 0
For **Fermions** the ladder operators obey anti-commutator laws:

.. math::
\{a_i, a^\dagger_j\} = a_i a^\dagger_j + a_j^\dagger a_i = \delta_{ij}\\
\{a^\dagger_i, a^\dagger_j\} = \{a_i, a_j\} = 0
Note that the fermionic laws imply $a_i^\dagger a_i^\dagger = 0$. This means that an operator, which tries to insert two particles in the same state will immidiately become 0 and therefore not contribute. This is known as `Pauli exclusion principle <https://en.wikipedia.org/wiki/Pauli_exclusion_principle>`_.

Within Qrisp it is currently only possible to model **fermions**, which is for many applications in chemistry the more important case. A modelling framework for bosons will follow in a future release. To start building a fermionic operator, we import the functions ``c`` and ``a`` for creators and annihilators.

::
from qrisp.operators import c, a
O = a(0)*c(1) + a(1)*a(2)
print(O)
# Yields: a0*c1 + a1*a2
To learn more how to build and manipulate these expressions, please look at the documentation page of :ref:`FermionicOperator`. For instance, the hermitian conjugate can be computed using the :meth:`.dagger <qrisp.operators.fermionic.FermionicOperator.dagger>` method.

::

print(O.dagger())
# Yields: a1*c0 + c2*c1

To apply the Pauli exclusion principle but also other anti-commutation laws for simplifaction, you can call the :meth:`reduce <qrisp.operators.FermionicOperator.reduce>` method.

::

O = a(0)*a(0) + a(1)*a(2) - a(2) * a(1)
print(O.reduce())
#Yields 2*a1*a2
The Jordan-Wigner embedding
^^^^^^^^^^^^^^^^^^^^^^^^^^^

A natural question that comes up is how to represent the ladder operators and the corresponding states on a quantum computer. The most established way to do this is to use the `Jordan-Wigner embedding <https://en.wikipedia.org/wiki/Jordan%E2%80%93Wigner_transformation>`_ (even though there are several `interesting alternatives <https://arxiv.org/abs/2212.09731>`_). The Jordan-Wigner embedding identifies each ladder term with an operator that acts on a qubit space:

.. math::
a_k = A_k \prod_{i = 0}^{k-1} Z_i
Where $A_k = \ket{0}\bra{1}$ and $Z_i$ are the Pauli-Z Operators. Feel free to verify that this indeed satisfies the anti-commutator relations! We can apply the Jordan-Wigner embedding with the corresponding method:

::

O_fermionic = a(4)
O_qubit = O_fermionic.to_qubit_operator(mapping_type = "jordan_wigner")
print(O_qubit)
# Yields: Z_0*Z_1*Z_2*Z_3*A_4

This gives us an instance of the :ref:`QubitOperator` class. What is the difference to a :ref:`FermionicOperator`? While FermionicOperators model the more abstract fermion space, qubit operators represent operators on the qubit space $(\mathbb{C}^2)^{\otimes n}$ and can be simulated and evaluated efficiently using a quantum computer. In particular, QubitOperators can represent tensor products of the following operators $X,Y,Z,A,C,P^0,P^1,I$. Make sure to read the :ref:`documentation <QubitOperator>` to learn about their definition!

Dynamics
^^^^^^^^

Both boson and fermion systems evolve under the Schrödinger equation:

.. math::
i \hbar \frac{d}{dt}\ket{\phi} = H \ket{\phi}
Where $H$ is a hermitian operator called Hamiltonian. *Hamiltonian simulation* is the procedure of mimicing the dynamics of a physical system described by a Hamiltonian $H$ using a quantum computer. In other words: creating the state $\ket{\phi, t} = \text{exp}(iHt)\ket{\phi, 0}$ artificially to evaluate some of its properties.

For bosonic systems, the Hamiltonian can only be a linear combination of products of the bosonic ladder operators. The equivalent holds for fermionic systems.

.. math::
H = \sum_{n=0}^{\infty} \sum_{i_1, \ldots, i_n} \sum_{j_1, \ldots, j_n} h_{i_1 \ldots i_n, j_1 \ldots j_n} a_{i_1}^{\dagger} \ldots a_{i_n}^{\dagger} a_{j_1} \ldots a_{j_n}
Where all $h \in \mathbb{R}$. An example Hamiltonian could therefore look like this

.. math::
H = h_{01}(a_0a^\dagger_1 + a_1a^\dagger_0) + h_{00}a_0a^\dagger_0
The particular values of the coefficients (like $h_{01}$ and $h_{00}$) are determined by the specifics of the system. For many systems of interest these numbers involve the computation of some integrals - a task that can be efficiently performed on the classical computer.


.. _molecular_data_tut:

Loading molecular data into Qrisp
=================================

If you don't feel like solving integrals right now, we've got you covered! Qrisp has a convenient interface to `PySCF <https://pyscf.org/>`_, which loads the molecular data directly as :ref:`FermionicOperator`. For that you need PySCF installed (``pip install pyscf``). If you're on Windows you might need to do some `WSL gymnastics <https://harshityadav95.medium.com/jupyter-notebook-in-windows-subsystem-for-linux-wsl-8b46fdf0a536>`_.

::

from pyscf import gto
mol = gto.M(atom = '''H 0 0 0; H 0 0 0.74''', basis = 'sto-3g')
H_ferm = FermionicOperator.from_pyscf(mol)
print(H_ferm)

This snippet uses the :meth:`.from_pyscf <qrisp.operators.fermionic.FermionicOperator.from_pyscf>` method to load the :ref:`FermionicOperator` representing the orbitals of the Dihydrogen molecule $H_2$. Or to be more precise, two hydrogen nuclei seperated by $0.74$ Angstrom. We take a look at the ladder operators:

::

-0.181210462015197*a0*a1*c2*c3 + 0.181210462015197*a0*c1*c2*a3
- 1.25330978664598*c0*a0 + 0.674755926814448*c0*a0*c1*a1
+ 0.482500939335616*c0*a0*c2*a2 + 0.663711401350814*c0*a0*c3*a3
+ 0.181210462015197*c0*a1*a2*c3 - 0.181210462015197*c0*c1*a2*a3
- 1.25330978664598*c1*a1 + 0.663711401350814*c1*a1*c2*a2
+ 0.482500939335616*c1*a1*c3*a3 - 0.475068848772178*c2*a2
+ 0.697651504490463*c2*a2*c3*a3 - 0.475068848772178*c3*a3

Or if preferred, the Jordan-Wigner embedding:

::

H_qubit = H_ferm.to_qubit_operator()
print(H_qubit)

::
0.181210462015197*A_0*A_1*C_2*C_3 - 0.181210462015197*A_0*C_1*C_2*A_3
- 0.181210462015197*C_0*A_1*A_2*C_3 + 0.181210462015197*C_0*C_1*A_2*A_3
- 1.25330978664598*P^0_0 + 0.674755926814448*P^0_0*P^0_1
+ 0.482500939335616*P^0_0*P^0_2 + 0.663711401350814*P^0_0*P^0_3
- 1.25330978664598*P^0_1 + 0.663711401350814*P^0_1*P^0_2
+ 0.482500939335616*P^0_1*P^0_3 - 0.475068848772178*P^0_2
+ 0.697651504490463*P^0_2*P^0_3 - 0.475068848772178*P^0_3
.. _ham_sim_tut:

Performing Hamiltonian simulation
=================================

To perform Hamiltonian simulation, we use the :meth:`.trotterization <qrisp.operators.qubit.QubitOperator.trotterization>` method, which gives us a Python function that performs a simulation of the Hamiltonian on a :ref:`QuantumVariable`.

::

from qrisp import QuantumVariable
electron_state = QuantumVariable(4)
electron_state[:] = {"1100": 2**-0.5, "0001": 2**-0.5}

This snippet initializes the state $\ket{\phi, t = 0}$, which is a superposition of 2 electrons in the lower two orbitals and a state of 1 electron in the highest orbital. We now perform the Hamiltonian simulation:

::
U = H_ferm.trotterization()
U(electron_state, t = 100, steps = 20)
This snippets simulates the Dihydrogen molecule for $t = 100$ `atomic units of time <https://physics.nist.gov/cgi-bin/cuu/Value?aut>`_, i.e.

.. math::
\begin{align}
\ket{\phi, t = 100} &= U(t = 100)\ket{\phi, t =0}\\
&= \text{exp}(100iH)\ket{\phi, t =0}
\end{align}
Finally, we want to extract some physical quantity from our simulation. Our quantity of choice is the particle number operator:

.. math::
N = \sum_{i = 0}^n a^\dagger_i a_i
In python code:

::

N = sum(c(i)*a(i) for i in range(4))

For each state $i$, this operator leaves the "electron at $i$" state invariant (i.e. a +1 contribution) and maps the "no electron at $i$" state to a 0 contribution. Its eigenvalues therefore indicate the number of electrons in the system. To evaluate the expectation value $\bra{\phi} N \ket{\phi}$, we call the :meth:`.get_measurement <qrisp.operators.qubit.QubitOperator.get_measurement>` method.

::

expectation_value = N.get_measurement(electron_state, precision = 0.01)
print(expectation_value)
# Yields: 1.50440973329083
We see that the expectaction value is (almost) 1.5 because $(2+1)/2 = 1.5$, which is expected assuming that the dynamics under the given Hamiltonian doesn't create particles out of nowhere (or destroys them).
The value is not exact because of statistical noise - we can increase the precision (which will however require more measurements from the backend!):

::

print(N.get_measurement(electron_state, precision = 0.0001))
# Yields: 1.4999799028124874
This concludes our little tutorial on Hamiltonian simulation. We hope you could learn something and feel motivated to explore more systems and techniques! Make sure to also check out the :ref:`MolecularPotentialEnergyCurve` example to learn how to compute the ground state for both types of operators!

For your reference, we give the full code below:

::

# Loading molecular data
from qrisp.operators import a,c, FermionicOperator
from pyscf import gto
mol = gto.M(atom = '''H 0 0 0; H 0 0 0.74''', basis = 'sto-3g')
H_ferm = FermionicOperator.from_pyscf(mol)
# Initializing the quantum state
from qrisp import QuantumVariable
electron_state = QuantumVariable(4)
electron_state[:] = {"1100": 2**-0.5, "0001": 2**-0.5}

# Performing the simulation
U = H_ferm.trotterization()
U(electron_state, t = 100, steps = 20)
# Evaluating the number operator
N = sum(c(i)*a(i) for i in range(4))
expectation_value = N.get_measurement(electron_state, precision = 0.01)
print(expectation_value)
Loading

0 comments on commit 182f975

Please sign in to comment.