# **Deutsch-Jozsa with Cirq**

```python
pip install cirq
```

    Collecting cirq
      Downloading cirq-0.13.1-py3-none-any.whl (7.7 kB)
    Collecting cirq-google==0.13.1
      Downloading cirq_google-0.13.1-py3-none-any.whl (437 kB)
         |████████████████████████████████| 437 kB 52 kB/s s eta 0:00:01
    Collecting cirq-aqt==0.13.1
      Downloading cirq_aqt-0.13.1-py3-none-any.whl (18 kB)
    Collecting cirq-pasqal==0.13.1
      Downloading cirq_pasqal-0.13.1-py3-none-any.whl (29 kB)
    Collecting cirq-web==0.13.1
      Downloading cirq_web-0.13.1-py3-none-any.whl (328 kB)
         |████████████████████████████████| 328 kB 3.0 kB/s  eta 0:00:01
    Collecting cirq-core==0.13.1
      Downloading cirq_core-0.13.1-py3-none-any.whl (1.6 MB)
         |████████████████████████████████| 1.6 MB 10 kB/s s eta 0:00:01
    Collecting cirq-rigetti==0.13.1
      Downloading cirq_rigetti-0.13.1-py3-none-any.whl (55 kB)
         |████████████████████████████████| 55 kB 38 kB/s s eta 0:00:01
    Collecting cirq-ionq==0.13.1
      Downloading cirq_ionq-0.13.1-py3-none-any.whl (47 kB)
         |████████████████████████████████| 47 kB 23 kB/s s eta 0:00:01
    Requirement already satisfied: requests~=2.18 in /opt/conda/lib/python3.8/site-packages (from cirq-aqt==0.13.1->cirq) (2.27.1)
    Requirement already satisfied: numpy~=1.16 in /opt/conda/lib/python3.8/site-packages (from cirq-core==0.13.1->cirq) (1.22.2)
    Requirement already satisfied: tqdm in /opt/conda/lib/python3.8/site-packages (from cirq-core==0.13.1->cirq) (4.62.3)
    Requirement already satisfied: pandas in /opt/conda/lib/python3.8/site-packages (from cirq-core==0.13.1->cirq) (1.4.0)
    Requirement already satisfied: sympy in /opt/conda/lib/python3.8/site-packages (from cirq-core==0.13.1->cirq) (1.9)
    Requirement already satisfied: sortedcontainers~=2.0 in /opt/conda/lib/python3.8/site-packages (from cirq-core==0.13.1->cirq) (2.4.0)
    Requirement already satisfied: scipy in /opt/conda/lib/python3.8/site-packages (from cirq-core==0.13.1->cirq) (1.8.0)
    Requirement already satisfied: typing-extensions in /opt/conda/lib/python3.8/site-packages (from cirq-core==0.13.1->cirq) (4.0.1)
    Requirement already satisfied: matplotlib~=3.0 in /opt/conda/lib/python3.8/site-packages (from cirq-core==0.13.1->cirq) (3.5.1)
    Collecting duet~=0.2.0
      Downloading duet-0.2.3-py3-none-any.whl (30 kB)
    Requirement already satisfied: networkx~=2.4 in /opt/conda/lib/python3.8/site-packages (from cirq-core==0.13.1->cirq) (2.6.3)
    Collecting google-api-core[grpc]<2.0.0dev,>=1.14.0
      Downloading google_api_core-1.31.5-py2.py3-none-any.whl (93 kB)
         |████████████████████████████████| 93 kB 10 kB/s s eta 0:00:01
    Requirement already satisfied: protobuf>=3.13.0 in /opt/conda/lib/python3.8/site-packages (from cirq-google==0.13.1->cirq) (3.14.0)
    Collecting pydantic~=1.8.2
      Downloading pydantic-1.8.2-cp38-cp38-manylinux2014_x86_64.whl (13.7 MB)
         |████████████████████████████████| 13.7 MB 13 kB/s  eta 0:00:01
    Collecting httpcore~=0.11.1
      Downloading httpcore-0.11.1-py3-none-any.whl (52 kB)
         |████████████████████████████████| 52 kB 13 kB/s s eta 0:00:01
    Collecting retrying~=1.3.3
      Downloading retrying-1.3.3.tar.gz (10 kB)
    Collecting qcs-api-client~=0.8.0
      Downloading qcs_api_client-0.8.0-py3-none-any.whl (97 kB)
         |████████████████████████████████| 97 kB 45 kB/s s eta 0:00:01
    Collecting rfc3986~=1.5.0
      Downloading rfc3986-1.5.0-py2.py3-none-any.whl (31 kB)
    Requirement already satisfied: toml~=0.10.2 in /opt/conda/lib/python3.8/site-packages (from cirq-rigetti==0.13.1->cirq) (0.10.2)
    Collecting iso8601~=0.1.14
      Downloading iso8601-0.1.16-py2.py3-none-any.whl (10 kB)
    Collecting pyquil~=3.0.0
      Downloading pyquil-3.0.1-py3-none-any.whl (220 kB)
         |████████████████████████████████| 220 kB 44 kB/s s eta 0:00:01
    Requirement already satisfied: sniffio~=1.2.0 in /opt/conda/lib/python3.8/site-packages (from cirq-rigetti==0.13.1->cirq) (1.2.0)
    Collecting idna~=2.10
      Downloading idna-2.10-py2.py3-none-any.whl (58 kB)
         |████████████████████████████████| 58 kB 80 kB/s s eta 0:00:01
    Requirement already satisfied: six~=1.16.0 in /opt/conda/lib/python3.8/site-packages (from cirq-rigetti==0.13.1->cirq) (1.16.0)
    Requirement already satisfied: python-dateutil~=2.8.1 in /opt/conda/lib/python3.8/site-packages (from cirq-rigetti==0.13.1->cirq) (2.8.2)
    Collecting rfc3339~=6.2
      Downloading rfc3339-6.2-py3-none-any.whl (5.5 kB)
    Collecting certifi~=2021.5.30
      Downloading certifi-2021.5.30-py2.py3-none-any.whl (145 kB)
         |████████████████████████████████| 145 kB 80.3 MB/s eta 0:00:01
    Collecting attrs~=20.3.0
      Downloading attrs-20.3.0-py2.py3-none-any.whl (49 kB)
         |████████████████████████████████| 49 kB 55 kB/s s eta 0:00:01
    Collecting pyjwt~=1.7.1
      Downloading PyJWT-1.7.1-py2.py3-none-any.whl (18 kB)
    Collecting h11~=0.9.0
      Downloading h11-0.9.0-py2.py3-none-any.whl (53 kB)
         |████████████████████████████████| 53 kB 14 kB/s s eta 0:00:01
    Collecting httpx~=0.15.5
      Downloading httpx-0.15.5-py3-none-any.whl (65 kB)
         |████████████████████████████████| 65 kB 36 kB/s s eta 0:00:01
    Requirement already satisfied: packaging>=14.3 in /opt/conda/lib/python3.8/site-packages (from google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq-google==0.13.1->cirq) (21.3)
    Requirement already satisfied: setuptools>=40.3.0 in /opt/conda/lib/python3.8/site-packages (from google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq-google==0.13.1->cirq) (60.8.2)
    Collecting googleapis-common-protos<2.0dev,>=1.6.0
      Downloading googleapis_common_protos-1.54.0-py2.py3-none-any.whl (207 kB)
         |████████████████████████████████| 207 kB 82.4 MB/s eta 0:00:01
    Collecting google-auth<2.0dev,>=1.25.0
      Downloading google_auth-1.35.0-py2.py3-none-any.whl (152 kB)
         |████████████████████████████████| 152 kB 78.1 MB/s eta 0:00:01
    Requirement already satisfied: pytz in /opt/conda/lib/python3.8/site-packages (from google-api-core[grpc]<2.0.0dev,>=1.14.0->cirq-google==0.13.1->cirq) (2021.3)
    Collecting grpcio<2.0dev,>=1.29.0
      Downloading grpcio-1.43.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.1 MB)
         |████████████████████████████████| 4.1 MB 54 kB/s s eta 0:00:01
    Collecting cachetools<5.0,>=2.0.0
      Downloading cachetools-4.2.4-py3-none-any.whl (10 kB)
    Collecting rsa<5,>=3.1.4
      Downloading rsa-4.8-py3-none-any.whl (39 kB)
    Collecting pyasn1-modules>=0.2.1
      Downloading pyasn1_modules-0.2.8-py2.py3-none-any.whl (155 kB)
         |████████████████████████████████| 155 kB 85.7 MB/s eta 0:00:01
    Requirement already satisfied: pillow>=6.2.0 in /opt/conda/lib/python3.8/site-packages (from matplotlib~=3.0->cirq-core==0.13.1->cirq) (9.0.1)
    Requirement already satisfied: kiwisolver>=1.0.1 in /opt/conda/lib/python3.8/site-packages (from matplotlib~=3.0->cirq-core==0.13.1->cirq) (1.3.2)
    Requirement already satisfied: fonttools>=4.22.0 in /opt/conda/lib/python3.8/site-packages (from matplotlib~=3.0->cirq-core==0.13.1->cirq) (4.29.1)
    Requirement already satisfied: cycler>=0.10 in /opt/conda/lib/python3.8/site-packages (from matplotlib~=3.0->cirq-core==0.13.1->cirq) (0.11.0)
    Requirement already satisfied: pyparsing>=2.2.1 in /opt/conda/lib/python3.8/site-packages (from matplotlib~=3.0->cirq-core==0.13.1->cirq) (2.4.7)
    Collecting pyasn1<0.5.0,>=0.4.6
      Downloading pyasn1-0.4.8-py2.py3-none-any.whl (77 kB)
         |████████████████████████████████| 77 kB 62 kB/s s eta 0:00:01
    Collecting rpcq<4.0.0,>=3.6.0
      Downloading rpcq-3.9.2.tar.gz (43 kB)
         |████████████████████████████████| 43 kB 17 kB/s s eta 0:00:01
    Collecting retry<0.10.0,>=0.9.2
      Downloading retry-0.9.2-py2.py3-none-any.whl (8.0 kB)
    Collecting lark<0.12.0,>=0.11.1
      Downloading lark-0.11.3.tar.gz (229 kB)
         |████████████████████████████████| 229 kB 43 kB/s s eta 0:00:01
    Requirement already satisfied: charset-normalizer~=2.0.0 in /opt/conda/lib/python3.8/site-packages (from requests~=2.18->cirq-aqt==0.13.1->cirq) (2.0.11)
    Requirement already satisfied: urllib3<1.27,>=1.21.1 in /opt/conda/lib/python3.8/site-packages (from requests~=2.18->cirq-aqt==0.13.1->cirq) (1.26.8)
    Collecting py<2.0.0,>=1.4.26
      Downloading py-1.11.0-py2.py3-none-any.whl (98 kB)
         |████████████████████████████████| 98 kB 78 kB/s s eta 0:00:01
    Requirement already satisfied: decorator>=3.4.2 in /opt/conda/lib/python3.8/site-packages (from retry<0.10.0,>=0.9.2->pyquil~=3.0.0->cirq-rigetti==0.13.1->cirq) (5.1.1)
    Collecting msgpack<1.0,>=0.6
      Downloading msgpack-0.6.2.tar.gz (119 kB)
         |████████████████████████████████| 119 kB 86.5 MB/s eta 0:00:01
    Collecting python-rapidjson
      Downloading python_rapidjson-1.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (1.5 MB)
         |████████████████████████████████| 1.5 MB 68.7 MB/s eta 0:00:01
    Requirement already satisfied: pyzmq>=17 in /opt/conda/lib/python3.8/site-packages (from rpcq<4.0.0,>=3.6.0->pyquil~=3.0.0->cirq-rigetti==0.13.1->cirq) (22.3.0)
    Requirement already satisfied: ruamel.yaml in /opt/conda/lib/python3.8/site-packages (from rpcq<4.0.0,>=3.6.0->pyquil~=3.0.0->cirq-rigetti==0.13.1->cirq) (0.17.19)
    Requirement already satisfied: ruamel.yaml.clib>=0.2.6 in /opt/conda/lib/python3.8/site-packages (from ruamel.yaml->rpcq<4.0.0,>=3.6.0->pyquil~=3.0.0->cirq-rigetti==0.13.1->cirq) (0.2.6)
    Requirement already satisfied: mpmath>=0.19 in /opt/conda/lib/python3.8/site-packages (from sympy->cirq-core==0.13.1->cirq) (1.2.1)
    Building wheels for collected packages: lark, retrying, rpcq, msgpack
      Building wheel for lark (setup.py) ... done
      Created wheel for lark: filename=lark-0.11.3-py2.py3-none-any.whl size=99647 sha256=b29f78b9065cdfa30a1552b95bfc80a7c8d66ae85a60ddff0c88ea3c905ba273
      Stored in directory: /home/jovyan/.cache/pip/wheels/34/cb/6c/4df359c2a3f0a1af4cccae6392bee423bb5aff530103de3538
      Building wheel for retrying (setup.py) ... done
      Created wheel for retrying: filename=retrying-1.3.3-py3-none-any.whl size=11447 sha256=9bb6531c02e2235c58c32b4379790fb5c30491eef07cd564a6f385ef0f05a616
      Stored in directory: /home/jovyan/.cache/pip/wheels/c4/a7/48/0a434133f6d56e878ca511c0e6c38326907c0792f67b476e56
      Building wheel for rpcq (setup.py) ... done
      Created wheel for rpcq: filename=rpcq-3.9.2-py3-none-any.whl size=45877 sha256=26ff87158b2e9d905134741504aff6767c87e8afc223ddaf184ab37a94a7844a
      Stored in directory: /home/jovyan/.cache/pip/wheels/20/fd/8d/4d4a9f389a9c92210dbee8ca8bbd725a6204f64a8ca8cad841
      Building wheel for msgpack (setup.py) ... done
      Created wheel for msgpack: filename=msgpack-0.6.2-cp38-cp38-linux_x86_64.whl size=345363 sha256=b87c67ab3a15a7c36461d46d544210143b2f0299e9c4a127f8c2b9fd61c8fffb
      Stored in directory: /home/jovyan/.cache/pip/wheels/5d/f2/04/0d19c10080b996bef17c908a6243e6e65d8da1a4094a3f604d
    Successfully built lark retrying rpcq msgpack
    Installing collected packages: rfc3986, pyasn1, idna, h11, rsa, pyasn1-modules, httpcore, certifi, cachetools, rfc3339, retrying, python-rapidjson, pyjwt, pydantic, py, msgpack, iso8601, httpx, googleapis-common-protos, google-auth, attrs, rpcq, retry, qcs-api-client, lark, grpcio, google-api-core, duet, pyquil, cirq-core, cirq-web, cirq-rigetti, cirq-pasqal, cirq-ionq, cirq-google, cirq-aqt, cirq
      Attempting uninstall: idna
        Found existing installation: idna 3.3
        Uninstalling idna-3.3:
          Successfully uninstalled idna-3.3
      Attempting uninstall: certifi
        Found existing installation: certifi 2021.10.8
        Uninstalling certifi-2021.10.8:
          Successfully uninstalled certifi-2021.10.8
      Attempting uninstall: pyjwt
        Found existing installation: PyJWT 2.3.0
        Uninstalling PyJWT-2.3.0:
          Successfully uninstalled PyJWT-2.3.0
      Attempting uninstall: msgpack
        Found existing installation: msgpack 1.0.3
        Uninstalling msgpack-1.0.3:
          Successfully uninstalled msgpack-1.0.3
      Attempting uninstall: attrs
        Found existing installation: attrs 21.4.0
        Uninstalling attrs-21.4.0:
          Successfully uninstalled attrs-21.4.0
    Successfully installed attrs-21.4.0 cachetools-4.2.4 certifi-2021.5.30 cirq-0.13.1 cirq-aqt-0.13.1 cirq-core-0.13.1 cirq-google-0.13.1 cirq-ionq-0.13.1 cirq-pasqal-0.13.1 cirq-rigetti-0.13.1 cirq-web-0.13.1 duet-0.2.3 google-api-core-1.31.5 google-auth-1.35.0 googleapis-common-protos-1.54.0 grpcio-1.43.0 h11-0.9.0 httpcore-0.11.1 httpx-0.15.5 idna-3.3 iso8601-0.1.16 lark-0.11.3 msgpack-0.6.2 py-1.11.0 pyasn1-0.4.8 pyasn1-modules-0.2.8 pydantic-1.8.2 pyjwt-1.7.1 pyquil-3.0.1 python-rapidjson-1.5 qcs-api-client-0.8.0 retry-0.9.2 retrying-1.3.3 rfc3339-6.2 rfc3986-1.5.0 rpcq-3.9.2 rsa-4.8
    Note: you may need to restart the kernel to use updated packages.

```python
import cirq
import numpy as np
import matplotlib.pyplot as plt
```

    <frozen importlib._bootstrap>:219: RuntimeWarning: scipy._lib.messagestream.MessageStream size changed, may indicate binary incompatibility. Expected 56 from C header, got 64 from PyObject

```python
print(cirq.google.Bristlecone)
```

                                                 (0, 5)────(0, 6)
                                                 │         │
                                                 │         │
                                        (1, 4)───(1, 5)────(1, 6)────(1, 7)
                                        │        │         │         │
                                        │        │         │         │
                               (2, 3)───(2, 4)───(2, 5)────(2, 6)────(2, 7)───(2, 8)
                               │        │        │         │         │        │
                               │        │        │         │         │        │
                      (3, 2)───(3, 3)───(3, 4)───(3, 5)────(3, 6)────(3, 7)───(3, 8)───(3, 9)
                      │        │        │        │         │         │        │        │
                      │        │        │        │         │         │        │        │
             (4, 1)───(4, 2)───(4, 3)───(4, 4)───(4, 5)────(4, 6)────(4, 7)───(4, 8)───(4, 9)───(4, 10)
             │        │        │        │        │         │         │        │        │        │
             │        │        │        │        │         │         │        │        │        │
    (5, 0)───(5, 1)───(5, 2)───(5, 3)───(5, 4)───(5, 5)────(5, 6)────(5, 7)───(5, 8)───(5, 9)───(5, 10)───(5, 11)
             │        │        │        │        │         │         │        │        │        │
             │        │        │        │        │         │         │        │        │        │
             (6, 1)───(6, 2)───(6, 3)───(6, 4)───(6, 5)────(6, 6)────(6, 7)───(6, 8)───(6, 9)───(6, 10)
                      │        │        │        │         │         │        │        │
                      │        │        │        │         │         │        │        │
                      (7, 2)───(7, 3)───(7, 4)───(7, 5)────(7, 6)────(7, 7)───(7, 8)───(7, 9)
                               │        │        │         │         │        │
                               │        │        │         │         │        │
                               (8, 3)───(8, 4)───(8, 5)────(8, 6)────(8, 7)───(8, 8)
                                        │        │         │         │
                                        │        │         │         │
                                        (9, 4)───(9, 5)────(9, 6)────(9, 7)
                                                 │         │
                                                 │         │
                                                 (10, 5)───(10, 6)

    /tmp/ipykernel_361/1631590671.py:1: DeprecationWarning: cirq.google was used but is deprecated.
     it will be removed in cirq v0.14.
     Use cirq_google instead.

      print(cirq.google.Bristlecone)

The following code gives the operations for these functions where we take two input qubits and compute the function in the third qubit.

```python
import warnings
warnings.filterwarnings('ignore')

"""Operations to query all possible functions on two bits.
Two of these functions are constant, and six of these functions are balanced.
"""
# Define three qubits to use.
q0, q1, q2 = cirq.LineQubit.range(3)

# Define the operations to query each of the two constant functions.
constant = (
    [],
    [cirq.X(q2)]
)

# Define the operations to query each of the six balanced functions.
balanced = (
    [cirq.CNOT(q0, q2)],
    [cirq.CNOT(q1, q2)],
    [cirq.CNOT(q0, q2), cirq.CNOT(q1, q2)],
    [cirq.CNOT(q0, q2), cirq.X(q2)],
    [cirq.CNOT(q1, q2), cirq.X(q2)],
    [cirq.CNOT(q0, q2), cirq.CNOT(q1, q2), cirq.X(q2)]
)
```

```python
# Define a function

def deJo_circuit(oracle):
    # Phase kickback trick.
    yield cirq.X(q2), cirq.H(q2)

    # Get an equal superposition over input bits.
    yield cirq.H(q0), cirq.H(q1)

    # Query the function.
    yield oracle

    # Use interference to get result, put last qubit into |1>.
    yield cirq.H(q0), cirq.H(q1), cirq.H(q2)

    # Use a final OR gate to put result in final qubit.
    yield cirq.X(q0), cirq.X(q1), cirq.CCX(q0, q1, q2)
    yield cirq.measure(q2)
```

```python
"""Verify """
simulator = cirq.Simulator()

print("\nResult on constant functions:")
for oracle in constant:
    result = simulator.run(cirq.Circuit(deJo_circuit(oracle)), repetitions=10)
    print(result)

print("\nResult on balanced functions:")
for oracle in balanced:
    result = simulator.run(cirq.Circuit(deJo_circuit(oracle)), repetitions=10)
    print(result)
```

    Result on constant functions:
    2=0000000000
    2=0000000000

    Result on balanced functions:
    2=1111111111
    2=1111111111
    2=1111111111
    2=1111111111
    2=1111111111
    2=1111111111

Project Files/ML Files/Deutsch-Jozsa with Cirq.ipynb


###### Content under Creative Commons Attribution license CC-BY 4.0, code under MIT license (c)2014 L.A. Barba, C. Cooper, G.F. Forsyth, A. Krishnan.

# Phugoid Motion

Welcome to [**"Practical Numerical Methods with Python!"**](http://openedx.seas.gwu.edu/courses/GW/MAE6286/2014_fall/about) This course is a collaborative, online, open education project, where we aim to give a foundation in scientific computing. The focus is on numerical solution of problems modeled by ordinary and partial differential equations.

This IPython Notebook introduces the problem we'll be studying in the **first module** of the course: the _phugoid model of glider flight_. We'll start with some background, explaining the physics, and working out the mathematical model.

First, we'll look at an idealized motion where there is no drag, resulting in a simple harmonic motion. We can plot some interesting trajectories that will pique your imagination. In the next notebook, you'll learn to numerically integrate the differential equation using Euler's method. But hang on ... first things first.

The term "phugoid" is used in aeronautics to refer to a motion pattern where an aircraft oscillates up and down —nose-up and climb, then nose-down and descend— around an equilibrium trajectory.  The aircraft oscillates in altitude, speed and pitch, with only small (neglected) variations in the angle of attack, as it repeatedly exchanges kinetic and potential energy.

A low-amplitude phugoid motion can be just a nuisance, as the aircraft does not exceed the stall angle of attack and nothing bad happens. But the mode can also be unstable leading to a stall or even a loop!

Look at this video showing a Cessna single-engine airplane in phugoid motion:

```python
from IPython.display import YouTubeVideo
YouTubeVideo('ysdU4mnRYdM')
```

That doesn't look too good! What's happening?

It can get a lot worse when an aircraft enters one of these modes that is unstable. For example, one of [NASA's Helios Solar Powered Aircraft](http://www.nasa.gov/centers/dryden/history/pastprojects/Helios/) prototype broke up in mid air due to extreme phugoid oscillations!

Helios was a proof-of-concept solar electric-powered flying wing that broke the world altitude record for a non-rocket-powered aircraft in August 2001. But in June 26, 2003, it broke something else. The aircraft entered phugoid motion after encountering turbulence near the Hawaiian Island of Kauai. The high speed in the oscillatory movement exceeded the design limits, and it ended up wrecked in the Pacific Ocean. Luckily, the Helios was remotely operated, and nobody got hurt.

## The physics of phugoids

The phugoid oscillation has the aircraft pitching up and down, as it decelerates and accelerates. The trajectory might look like a sinusoid, as in the figure below. The assumption is that the forward velocity of the aircraft, $v$, varies in such a way that the angle of attack remains (nearly) constant, which means that we can assume a constant lift coefficient.

####Figure 1. Trajectory of an aircraft in phugoid motion.

In the descending portion of the trajectory, the aircraft's velocity increases as it proceeds from a peak to the minimum height—gaining kinetic energy at the expense of potential energy. The contrary happens in the upward segment, as its velocity decreases there.

We measure the pitch angle (between the aircraft's longitudinal axis and the horizontal) as positive when the aircraft's nose is pointing up. In the portion of the trajectory below the center-line, where it curves upwards, the pitch angle $\theta$ is increasing: $\dot{\theta}>0$. And where the trajectory curves down, the pitch angle is decreasing: $\dot{\theta}<0$, as shown in the figure.

Let's remind ourselves of the forces affecting an aircraft in a downward glide. Look at the figure below: we show the flight path, the forces on the glider (no thrust), and the _glide angle_ or flight path angle, $\gamma$, between the flight path and the horizontal.

####Figure 2. Forces on a glider.

The force of lift, $L$ —created by the airflow around the wings— is perpendicular to the trajectory, and the force of drag, $D$, is parallel to the trajectory. Both forces are expressed in terms of coefficients of lift and drag, $C_L$ and $C_D$, respectively, that depend on the wing design and _angle of attack_—the angle between the wing chord and the flight path.

If you are not familiar with airplane aerodynamics, you might be getting confused with some terms here ... and all those angles! But be patient and look things up, if you need to. We're giving you a quick summary here.

Lift and drag are proportional to a surface area, $S$, and the dynamic pressure: $1/2 \rho v^2$, where $\rho$ is the density of air, and $v$ the forward velocity of the aircraft. The equations for lift and drag are:

$$\begin{eqnarray}
L &=& C_L S \times \frac{1}{2} \rho v^2 \\
D &=& C_D S \times \frac{1}{2} \rho v^2
\end{eqnarray}$$

If the glider were in equilibrium, the forces would balance each other. We can equate the forces in the directions perpendicular and parallel to the trajectory, as follows:

$$\begin{equation}
L = W \cos \gamma \quad \text{and} \quad D = W \sin \gamma
\end{equation}$$

where $W$ repesents the weight of the glider.

In the figure, we've drawn the angle $\gamma$ as the _glide angle_, formed between the direction of motion and the horizontal. We are not bothered with the _sign_ of the angle, because we draw a free-body diagram and take the direction of the forces into account in writing our balance equations. But later on, we will need to be careful with the sign of the angles. It can cause you a real headache to keep this straight, so be patient!

It looks like we've set this up to do a little bit of mathematics. Are you ready?

But before, a short glimpse of the history.

## Lanchester's Aerodonetics

"Phugoid theory" was first described by the British engineer Frederick W. Lanchester in _"Aerodonetics"_ (1909). This book is so old that it is now in the public domain, so you can actually download [from Google Books](http://books.google.com/books?id=6hxDAAAAIAAJ&dq=%22phugoid%20theory%20deals%20with%20the%20longitudinal%20stability%22&pg=PA37#v=onepage&q=%22phugoid%20theory%20deals%20with%20the%20longitudinal%20stability%22&f=false) a PDF file of a scan, or read it online.

Lanchester defines phugoid theory as the study of longitudinal stability of a flying machine (aerodone). He first considered the simplification where drag and moment of inertia are neglected. Then he included these effects, obtaining an equation of stability. In addition to describing many experiments by himself and others, Lanchester also reports on _"numerical work ... done by the aid of an ordinary 25-cm slide rule."_ Go figure!

### Ideal case of zero drag

In this section, we follow the derivation given by Milne-Thompson (1966), which we find a little bit easier than that of the original in "Aerodonetics"!

An aircraft flying in a steady, straight horizontal flight has a lift equal to its weight. The velocity in this condition is sometimes called _trim velocity_ ("trim" is what pilots do to set the controls to just stay in a steady flight). Let's use $v_t$ for the trim velocity, and from $L=W$ deduce that:

$$\begin{equation}
W = C_L S \times\frac{1}{2} \rho v_t^2
\end{equation}$$

The weight $W$ is constant for the aircraft, but the lift at any other flight condition depends on the flight speed, $v$. We can use the expression for the weight in terms of $v_t$ to obtain the ratio $L/W$ at any other flight velocity, as follows:

$$\begin{equation}
\frac{L}{W}= \frac{v^2}{v_t^2}
\end{equation}$$

Imagine that the aircraft experienced a little upset, a wind gust, and it finds itself off the "trim" level, in a curved path with an instantaneous angle $\theta$. In the sketch below, we exaggerate the curved trajectory of flight to help you visualize what we'll do next. The angle $\theta$ (using the same name as Milne-Thompson) is between the _trajectory_ and the horizontal, positive up.

####Figure 3. Curved trajectory of the aircraft going up.

A balance of forces now has to take into account that our reference frame is moving with the aircraft, in a rotating frame: we have a centrifugal force. The balance in the direction of lift is thus:

$$\begin{equation}
L- W \cos \theta = \frac{W}{g} \frac{v^2}{R}
\end{equation}$$

where $R$ is the radius of curvature of the trajectory, and $g$ the acceleration of gravity. Recall that the centrifugal acceleration is $v^2/R$. Rearrange this by dividing the equation by the weight, and use the expression we found for $L/W$, above. The following equation results:

$$\begin{equation}
\frac{v^2}{v_t^2}-\cos \theta = \frac{v^2}{g R}
\end{equation}$$

Recall that we simplified the problem assuming that there is no friction, which means that the total energy is constant (the lift does no work). If $z$ represents the depth below a reference horizontal line, the energy per unit mass is (kinetic plus potential energy):

$$\begin{equation}
\frac{1}{2}v^2-g z = \text{constant}
\end{equation}$$

To get rid of that pesky constant, we can choose the reference horizontal line at the level that makes the constant energy equal to zero, so $v^2 = 2 g z$. That helps us re-write the phugoid equation in terms of $z$ as follows:

$$\begin{equation}
\frac{z}{z_t}-\cos \theta = \frac{2z}{R}
\end{equation}$$

Let $ds$ represent a small arc-length of the trajectory. We can write

$$\begin{equation}
\frac{1}{R} = \frac{d\theta}{ds} \quad \text{and}\quad  \sin\theta = -\frac{dz}{ds}
\end{equation}$$

Employing the chain rule of calculus,

$$\begin{equation}
\frac{1}{R} = \frac{d\theta}{ds} = \frac{dz}{ds}\frac{d\theta}{dz} = -\sin \theta\frac{d\theta}{dz}
\end{equation}$$

Multiply the phugoid equation by $\frac{1}{2\sqrt{z}}$ to get:

$$\begin{equation}
\frac{\sqrt{z}}{2z_t} - \frac{\cos\theta}{2\sqrt{z}} = \frac{\sqrt{z}}{R}
\end{equation}$$

Substituting for $1/R$ on the right hand side and bringing the cosine term over to the right, we get:

$$\begin{equation}
\frac{\sqrt{z}}{2z_t} = \frac{\cos \theta}{2 \sqrt{z}} - \sqrt{z} \sin \theta \frac{d\theta}{dz}
\end{equation}$$

The right-hand-side is an exact derivative! We can rewrite it as:

$$\begin{equation}
\frac{d}{dz} \left(\sqrt{z}\cos\theta \right) = \frac{\sqrt{z}}{2z_t}
\end{equation}$$

Integrating this equation, we add an arbitrary constant, chosen as $C\sqrt{z_t}$ which (after dividing through by $\sqrt{z}$) gives:

$$\begin{equation}
\cos \theta = \frac{1}{3}\frac{z}{z_t} + C\sqrt{\frac{z_t}{z}}
\end{equation}$$

Taking the derivative of both sides of equation (15) and applying the relations from equation (10) yields:

$$\begin{equation}
\frac{z_t}{R} = \frac{1}{3} - \frac{C}{2}\sqrt{\frac{z_t^3}{z^3}}
\end{equation}$$

Make sure you have followed the derivation, and perhaps write it out on paper!

## Phugoid Curves

Equation (15) is non-linear, which usually means we are hard-pressed to write a clean expression for the variable of interest, $z$.  In fact, Lanchester himself said that he was unable to _"reduce this expression to a form suitable for co-ordinate plotting."_ If the great polymath couldn't do it, we can't either!

But Lanchester _was_ able to plot a suitable approximation of the phugoid flight path using what he called the "trammel" method.  If you're interested in seeing how he did it, his explanation begins on page [48 of Aerodonetics](http://books.google.com/books?id=6hxDAAAAIAAJ&pg=PA49&lpg=PA48&dq=aerodonetics+the+use+of+the+trammel&source=bl&ots=lB6EVKYQuT&sig=aVE2kiDWZoWftaWczMIrcYftMOs&hl=en&sa=X&ei=gTD_U82fGYjzgwT3moGwCQ&ved=0CCAQ6AEwAA#v=onepage&q=aerodonetics%20the%20use%20of%20the%20trammel&f=false).  It's a trip.

Lanchester used Equations (15) and (16) to solve for the constant $C$ and the radius of curvature $R$ and then iteratively plotted small arcs of the phugoid path. By hand.

We wrote a neat little code that duplicates the manual trammel method, but it might be a bit much for you to absorb in the first lesson.  If you want to look it over, you are more than welcome to.  If you are just starting with Python, skip it for the moment and we'll return to it at the end of this module.

###Plotting the flight path

As we mentioned, we wrote a Python code to reproduce programmatically what Lanchester did graphically. Here's a neat feature of IPython Notebooks: you can run external programs with the magical keyword ... wait for it ... `run`. And the jargon of IPython _is_ to call this "magic." In fact, there are a bunch of [magic functions](http://ipython.org/ipython-doc/dev/interactive/tutorial.html) that you will learn about. They will make you a happy camper.

Let's do it:

```python
%run phugoid.py
%matplotlib inline
```

This code cell loaded our simulated-trammel code, `phugoid.py`. The code defined a function for you in the background, called `plot_flight_path`, taking three inputs: $z_t$, $z$ and $\theta$.

Look again at Equation (15), where we take the positive square root. There are several possibilities, depending on the value that the constant $C$ takes.

* There are no physical solutions for $C>2/3$, because it would result in $\cos\theta>1$.

* If $C=2/3$, then the solution is a horizontal straight line, because $\cos\theta=1$, $\theta=0$ and $R=\infty$.

* Any value of $C$ for which $0 < C < \frac{2}{3}$ will produce "trochoidal"-like paths.  What does this look like? Let's use our custom function `plot_flight_path` to find out!

```python
#zt = 64, z = 16, theta=0
plot_flight_path(64, 16, 0)
```

Cool!  Note that the plot title tells us what the calculated value of $C$ was for our input conditions.  We have a value of $C$ between $0$ and $\frac{2}{3}$ and our path is trochoidal, like we announced it would be.

* For negative values of $C$, the resultant flight path consists of a series of loops.  Let's try it out!

```python
plot_flight_path(64,16,numpy.pi)
```

You can play around with the input values and see what kind of behavior results.  Just note that any value of $C > \frac{2}{3}$ will result in $\cos \theta > 1$, which doesn't exist.  Python will probably throw a few errors if you hit that condition, but just try again!

* The last case is $C = 0$.  Take another look at Equation (16) and plug in $C = 0$, what should happen? It looks like it will just reduce to

$$R = 3z_t$$

It's a constant radius of curvature!  In fact, this solution is a series of semi-circles, with a cusp between them. One way to force $C = 0$ that we can figure out from Equation (15), is to make:

$$z = 3z_t\ \ \ ,\ \ \  \theta = 0$$

```python
plot_flight_path(16,48,0.)
```

That looks an awful lot like a quarter circle.  And what's the radius of the arc?  It's $$r = 48 = 3z_t.$$

We can also get a semi-circle out of our simulated trammel by changing to another configuration where $C$ is (near) zero. Here's one example:

```python
plot_flight_path(64,16,-numpy.pi/2)
```

That is so nice. We have reproduced the trajectories that Lanchester found more than a hundred years ago, painstakingly drawing them by hand with a contraption called a "trammel." It must have taken him days!

Here is how the different phugoid curves are drawn in von Kármán's book, _Aerodynamics_ (1957). He never says _how_ he drew them, but we're guessing by hand, also. We did pretty good!

####Figure 4. Phugoid curves in von Kármán (1957).

In the next notebook of this series, we'll look at the differential equation that arises when you consider small perturbations on the horizontal phugoid, and we'll learn to numerically integrate that to get the flight paths.

## References

1. Lanchester, F. W. _Aerodonetics_, D. van Nostrand Company: New York, 1909. On the public domain. [Get it from Google Books](http://books.google.com/books?id=6hxDAAAAIAAJ&pg=PP1#v=onepage&q&f=false).

2. Milne-Thompson, L. M. _Theoretical Aerodynamics_, Dover 2012 reprint of the revised 1966 edition. [Read on Google Books](http://books.google.com/books?id=EMfCAgAAQBAJ&lpg=PP1&pg=PP1#v=onepage&q&f=false) (see section 18.5)

3. Sinha, N. K. and Ananthkrishnan, N. _Elementary Flight Dynamics with an introduction to Bifurcation and Continuation Methods_, CRC Press, 2013. [Read on Google Books](http://books.google.com/books?id=yXL6AQAAQBAJ&lpg=PP1&pg=PP1#v=onepage&q&f=false) (see chapter 5)

4. von Kármán, T. _Aerodynamics_, Dover 2004 reprint of the 1957 2nd edition. (see pages 149–151)

## About this course

This course is a collaborative project in open education. Three professors across the world are teaching connected courses, developing and reviewing course materials, and interacting with the community of learners that follow the course online. They are:

* Lorena A. Barba, the George Washington University, United States
* Carlos Jerez, Pontificia Universidad Católica de Chile
* Ian Hawke, Southampton University, United Kingdom

---

######The cell below loads the style of this notebook.

```python
# Execute this cell to load the notebook's style sheet, then ignore it
from IPython.core.display import HTML
css_file = '../../styles/numericalmoocstyle.css'
HTML(open(css_file, "r").read())
```

<link href='http://fonts.googleapis.com/css?family=Alegreya+Sans:100,300,400,500,700,800,900,100italic,300italic,400italic,500italic,700italic,800italic,900italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Arvo:400,700,400italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=PT+Mono' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Shadows+Into+Light' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Nixie+One' rel='stylesheet' type='text/css'>
<style>

@font-face {
    font-family: "Computer Modern";
    src: url('http://mirrors.ctan.org/fonts/cm-unicode/fonts/otf/cmunss.otf');
}

#notebook_panel { /* main background */
    background: rgb(245,245,245);
}

div.cell { /* set cell width */
    width: 750px;
}

div #notebook { /* centre the content */
    background: #fff; /* white background for content */
    width: 1000px;
    margin: auto;
    padding-left: 0em;
}

#notebook li { /* More space between bullet points */
    margin-top:0.8em;
}

/* draw border around running cells */
div.cell.border-box-sizing.code_cell.running {
    border: 1px solid #111;
}

/* Put a solid color box around each cell and its output, visually linking them*/
div.cell.code_cell {
    background-color: rgb(256,256,256);
    border-radius: 0px;
    padding: 0.5em;
    margin-left:1em;
    margin-top: 1em;
}

div.text_cell_render{
    font-family: 'Alegreya Sans' sans-serif;
    line-height: 140%;
    font-size: 125%;
    font-weight: 400;
    width:600px;
    margin-left:auto;
    margin-right:auto;
}

/* Formatting for header cells */
.text_cell_render h1 {
    font-family: 'Nixie One', serif;
    font-style:regular;
    font-weight: 400;
    font-size: 45pt;
    line-height: 100%;
    color: rgb(0,51,102);
    margin-bottom: 0.5em;
    margin-top: 0.5em;
    display: block;
}

.text_cell_render h2 {
    font-family: 'Nixie One', serif;
    font-weight: 400;
    font-size: 30pt;
    line-height: 100%;
    color: rgb(0,51,102);
    margin-bottom: 0.1em;
    margin-top: 0.3em;
    display: block;
}

.text_cell_render h3 {
    font-family: 'Nixie One', serif;
    margin-top:16px;
    font-size: 22pt;
    font-weight: 600;
    margin-bottom: 3px;
    font-style: regular;
    color: rgb(102,102,0);
}

.text_cell_render h4 {    /*Use this for captions*/
    font-family: 'Nixie One', serif;
    font-size: 14pt;
    text-align: center;
    margin-top: 0em;
    margin-bottom: 2em;
    font-style: regular;
}

.text_cell_render h5 {  /*Use this for small titles*/
    font-family: 'Nixie One', sans-serif;
    font-weight: 400;
    font-size: 16pt;
    color: rgb(163,0,0);
    font-style: italic;
    margin-bottom: .1em;
    margin-top: 0.8em;
    display: block;
}

.text_cell_render h6 { /*use this for copyright note*/
    font-family: 'PT Mono', sans-serif;
    font-weight: 300;
    font-size: 9pt;
    line-height: 100%;
    color: grey;
    margin-bottom: 1px;
    margin-top: 1px;
}

.CodeMirror{
    font-family: "PT Mono";
    font-size: 90%;
}

</style>


# Введение в численные методы оптимизации (Ю. Е. Нестеров Введение в выпуклую оптимизацию, гл. 1 $\S$ 1.1)

 1. Обзор материала весеннего семестра
 2. Постановка задачи
 3. Общая схема решения
 4. Сравнение методов оптимизации
 5. Методы одномерной минимизации

## Обзор материала весеннего семестра

Также на [странице курса](https://github.com/amkatrutsa/MIPT-Opt#spring-term).

1. Методы решения задач **безусловной** оптимизации
    - Одномерная минимизация (**уже сегодня!**)
    - Градиентный спуск
    - Метод Ньютона
    - Квазиньютоновские методы
    - Метод сопряжённых градиентов
    - Опционально:
        - Решение задачи наименьших квадратов
        - Оптимальные методы и нижние оценки
2. Методы решения задач **условной** оптимизации
    - Методы проекции градиента и условного градиента
    - Методы штрафных и барьерных функций
    - Метод модифицированой функции Лагранжа

## Организация работы в семестре

1. Семинар и лекция раз в неделю
2. Два задания в течение семестра
3. Midterm в середине семестра
4. Итоговая контрольная в конце семестра
5. Экзамен в конце семестра (схема выставления оценки аналогична осеннему семестру)
6. Миниконтрольные в начале каждого семинара
7. Домашнее задание почти каждую неделю: $\TeX$ или Jupyter Notebook

## Постановка задачи

\begin{equation}
\begin{split}
& \min_{x \in S} f_0(x)\\
\text{s.t. } & f_j(x) = 0, \; j = 1,\ldots,m\\
& g_k(x) \leq 0, \; k = 1,\ldots,p
\end{split}
\end{equation}
где $S \subseteq \mathbb{R}^n$, $f_j: S \rightarrow \mathbb{R}, \; j = 0,\ldots,m$, $g_k: S \rightarrow \mathbb{R}, \; k=1,\ldots,p$

Все функции как минимум непрерывны.

Важный факт: задачи **нелинейной** оптимизации

в их самой общей форме являются **численно неразрешимыми**!

## Аналитические результаты
- Необходимое условие первого порядка:

если $x^*$ точка локального минимума дифференцируемой функции $f(x)$, тогда
$$
f'(x^*) = 0
$$
- Необходимое условие второго порядка

если $x^*$ точка локального минимума дважды дифференцируемой функции $f(x)$, тогда

$$
f'(x^*) = 0 \quad \text{и} \quad f''(x^*) \succeq 0
$$
- Достаточное условие:

пусть $f(x)$ дважды дифференцируемая функция, и пусть точка $x^*$ удовлетворяет условиям

$$
f'(x^*) = 0 \quad f''(x^*) \succ 0,
$$
тогда $x^*$ является точкой строго локального минимума функции $f(x)$.

**Замечание**: убедитесь, что Вы понимаете, как доказывать эти

результаты!

## Особенности численного решения

1. Точно решить задачу принципиально невозможно из-за погрешности машинной арифметики
2. Необходимо задать критерий обнаружения решения
3. Необходимо определить, какую информацию о задаче использовать

## Общая итеративная схема

Дано: начальное приближение $x$, требуемая точность $\varepsilon$.

```python
def GeneralScheme(x, epsilon):

    while StopCriterion(x) > epsilon:

        OracleResponse = RequestOracle(x)

        UpdateInformation(I, x, OracleResponse)

        x = NextPoint(I, x)

    return x
```

### Вопросы
1. Какие критерии остановки могут быть?
2. Что такое оракул и зачем он нужен?
3. Что такое информационная модель?
4. Как вычисляется новая точка?

#### Критерии остановки
1. Сходимость по аргументу:
$$
\| x_k - x^*  \|_2 < \varepsilon
$$
2. Сходимость по функции:
$$
\| f_k - f^* \|_2 < \varepsilon
$$
3. Выполнение необходимого условия
$$
\| f'(x_k) \|_2 < \varepsilon
$$

Но ведь $x^*$ неизвестна!

Тогда

\begin{align*}
& \|x_{k+1} - x_k \| = \|x_{k+1} - x_k + x^* - x^* \| \leq \\
& \|x_{k+1} - x^* \| + \| x_k - x^* \| \leq 2\varepsilon
\end{align*}

Аналогично для сходимости по функции,

однако иногда можно оценить $f^*$!

**Замечание**: лучше использовать относительные изменения

этих величин!

Например $\dfrac{\|x_{k+1} - x_k \|_2}{\| x_k \|_2}$

#### Что такое оракул?
**Определение**: оракулом называют некоторое абстрактное

устройство, которое отвечает на последовательные вопросы

метода

Аналогия из ООП:

- оракул - это виртуальный метод базового класса
- каждая задача - производный класс
- оракул определяется для каждой задачи отдельно согласно общему определению в базовом классе

**Концепция чёрного ящика**
1. Единственной информацией, получаемой в ходе работы итеративного метода, являются ответы оракула
2. Ответы оракула являются *локальными*

#### Информация о задаче
1. Каждый ответ оракула даёт **локальную** информацию о поведении функции в точке
2. Агрегируя все полученные ответы оракула, обновляем информацию о **глобальном** виде целевой функции:
    - кривизна
    - направление убывания
    - etc

#### Вычисление следующей точки

$$
x_{k+1} = x_{k} + \alpha_k h_k
$$

- **Линейный поиск**: фиксируется направление $h_k$ и производится поиск по этому направлению "оптимального" значения  $\alpha_k$
- **Метод доверительных областей**: фиксируется допустимый размер *области* по некоторой норме $\| \cdot  \| \leq \alpha$ и *модель* целевой функции, которая хорошо её аппроксимирует в выбранной области.

    Далее производится поиск направления $h_k$, минимизирующего модель целевой функции и не выводящего точку $x_k + h_k$ за пределы доверительной области

#### Вопросы
1. Как выбрать $\alpha_k$?
2. Как выбрать $h_k$?
3. Как выбрать модель?
4. Как выбрать область?
5. Как выбрать размер области?

    В курсе рассматривается только линейный поиск!

Однако несколько раз копцепция метода доверительных областей

будет использована.

## Как сравнивать методы оптимизации?
Для заданного класса задач сравнивают следующие величины:
1. Сложность
    - аналитическая: число обращений к оракулу для решения задачи с точностью $\varepsilon$
    - арифметическая: общее число всех вычислений, необходимых для решения задачи с точностью $\varepsilon$
2. Скорость сходимости
3. Эксперименты

### Скорости сходимости
1. Сублинейная
$$
\| x_{k+1} - x^* \|_2 \leq C k^{\alpha},
$$
где $\alpha < 0$ и $ 0 < C < \infty$
2. Линейная (геометрическая прогрессия)
$$
\| x_{k+1} - x^* \|_2 \leq Cq^k,
$$
где $q \in (0, 1)$ и $ 0 < C < \infty$

3. Сверхлинейная
$$
\| x_{k+1} - x^* \|_2 \leq Cq^{k^p},
$$
где $q \in (0, 1)$, $ 0 < C < \infty$ и $p > 1$
4. Квадратичная
$$
\| x_{k+1} - x^* \|_2 \leq C\| x_k - x^* \|^2_2, \qquad \text{или} \qquad \| x_{k+1} - x^* \|_2 \leq C q^{2^k}
$$
где $q \in (0, 1)$ и $ 0 < C < \infty$

### Оптимальные методы: can we do better?
- Доказывают нижние оценки скоростей сходимости для класса задач и методов фиксированного порядка
- Предлагают методы, на которых эти нижние оценки достигаются $\Rightarrow$ доказана оптимальность
- Ниже про значение теорем сходимости

Оптимальным методам и нижним оценкам будет, возможно,

посвящён отдельный семинар или часть домашнего задания.

```python
%matplotlib inline
import matplotlib.pyplot as plt

USE_COLAB = False
if not USE_COLAB:
    plt.rc("text", usetex=True)

import numpy as np
C = 10
alpha = -0.5
q = 0.9
num_iter = 7
sublinear = np.array([C * k**alpha for k in range(1, num_iter + 1)])
linear = np.array([C * q**k for k in range(1, num_iter + 1)])
superlinear = np.array([C * q**(k**2) for k in range(1, num_iter + 1)])
quadratic = np.array([C * q**(2**k) for k in range(1, num_iter + 1)])
plt.figure(figsize=(12,8))
plt.semilogy(np.arange(1, num_iter+1), sublinear,
             label=r"Sublinear, $\alpha = -0.5$")
plt.semilogy(np.arange(1, num_iter+1), superlinear,
             label=r"Superlinear, $q = 0.5, p=2$")
plt.semilogy(np.arange(1, num_iter+1), linear,
             label=r"Linear, $q = 0.5$")
plt.semilogy(np.arange(1, num_iter+1), quadratic,
             label=r"Quadratic, $q = 0.5$")
plt.xlabel("Number of iterations, $k$", fontsize=28)
plt.ylabel("Error rate upper bound", fontsize=28)
plt.legend(loc="best", fontsize=26)
plt.xticks(fontsize = 28)
_ = plt.yticks(fontsize = 28)
```

### Значение теорем сходимости (Б.Т. Поляк Введение в оптимизацию, гл. 1, $\S$ 6)
1. Что дают теоремы сходимости
     - класс задач, для которых можно рассчитывать на применимость метода (важно не завышать условия!)
         - выпуклость
         - гладкость
     - качественное поведение метода
         - существенно ли начальное приближение
         - по какому функционалу есть сходимость
     - оценку скорости сходимости
         - теоретическая оценка поведения метода без проведения экспериментов
         - определение факторов, которые влияют на сходимость (обусловленность, размерность, etc)
         - иногда заранее можно выбрать число итераций для достижения заданной точности

2. Что **НЕ** дают теоремы сходимости
    - сходимость метода **ничего не говорит** о целесообразности его применения
    - оценки сходимости зависят от неизвестных констант - неконструктивный характер
    - учёт ошибок округления и точности решения вспомогательных задач

**Мораль**: нужно проявлять разумную осторожность

и здравый смысл!

## Классификация задач
1. Безусловная оптимизация
    - целевая функция липшицева
    - градиент целевой функции липшицев
2. Условная оптимизация
    - многогранник
    - множество простой структуры
    - общего вида

## Классификация методов
1. Методы нулевого порядка: оракул возвращает только значение функции $f(x)$

2. Методы первого порядка: оракул возвращает значение функции $f(x)$ и её градиент $f'(x)$

3. Методы второго порядка: оракул возвращает значение функции $f(x)$, её градиент $f'(x)$ и гессиан $f''(x)$.

**Вопрос**: существуют ли методы более высокого порядка?

1. Одношаговые методы
$$
x_{k+1} = \Phi(x_k)
$$
2. Многошаговые методы
$$
x_{k+1} = \Phi(x_k, x_{k-1}, ...)
$$

## Одномерная минимизация
**Определение**. Функция $f(x)$ называется унимодальной на $[a, b]$, если существует такая точка $x^* \in [a, b]$, что
- $f(x_1) > f(x_2)$ для любых $a \leq x_1 < x_2 < x^*$,

и
- $f(x_1) < f(x_2)$ для любых $x^* < x_1 < x_2 \leq b$.

**Вопрос**: какая геометрия унимодальных функций?

### Метод дихотомии

Идея из информатики первого семестра:

делим отрезок $[a,b]$ на две равные части

пока не найдём минимум унимодальной функции.

- $N$ - число вычислений функции $f$
- $K = \frac{N - 1}{2}$ - число итераций

Тогда
$$
|x_{K+1} - x^*| \leq \frac{b_{K+1} - a_{K+1}}{2} = \left( \frac{1}{2} \right)^{\frac{N-1}{2}} (b - a) \approx 0.5^{K} (b - a)
$$

```python
def binary_search(f, a, b, epsilon, callback=None):
    c = (a + b) / 2.0
    while abs(b - a) > epsilon:
#         Check left subsegment
        y = (a + c) / 2.0
        if f(y) <= f(c):
            b = c
            c = y
        else:
#         Check right subsegment
            z = (b + c) / 2.0
            if f(c) <= f(z):
                a = y
                b = z
            else:
                a = c
                c = z
        if callback is not None:
            callback(a, b)
    return c
```

```python
def my_callback(a, b, left_bound, right_bound, approximation):
    left_bound.append(a)
    right_bound.append(b)
    approximation.append((a + b) / 2.0)
```

```python
import numpy as np

left_boud_bs = []
right_bound_bs = []
approximation_bs = []

callback_bs = lambda a, b: my_callback(a, b,
            left_boud_bs, right_bound_bs, approximation_bs)

# Target unimodal function on given segment
f = lambda x: (x - 2) * x * (x + 2)**2 # np.power(x+2, 2)
# f = lambda x: -np.sin(x)
x_true = -2
# x_true = np.pi / 2.0
a = -3
b = -1.5
epsilon = 1e-8
x_opt = binary_search(f, a, b, epsilon, callback_bs)
print(np.abs(x_opt - x_true))
plt.figure(figsize=(10,6))
plt.plot(np.linspace(a,b), f(np.linspace(a,b)))
plt.title("Objective function", fontsize=28)
plt.xticks(fontsize = 28)
_ = plt.yticks(fontsize = 28)
```

### Метод золотого сечения
Идея:

делить отрезок $[a,b]$ не на две равные насти,

а в пропорции "золотого сечения".

Оценим скорость сходимости аналогично методу дихотомии:

$$
|x_{K+1} - x^*| \leq b_{K+1} - a_{K+1} = \left( \frac{1}{\tau} \right)^{N-1} (b - a) \approx 0.618^K(b-a),
$$
где $\tau = \frac{\sqrt{5} + 1}{2}$.

- Константа геометрической прогрессии **больше**, чем у метода дихотомии
- Количество вызовов функции **меньше**, чем у метода дихотомии

```python
def golden_search(f, a, b, tol=1e-5, callback=None):
    tau = (np.sqrt(5) + 1) / 2.0
    y = a + (b - a) / tau**2
    z = a + (b - a) / tau
    while b - a > tol:
        if f(y) <= f(z):
            b = z
            z = y
            y = a + (b - a) / tau**2
        else:
            a = y
            y = z
            z = a + (b - a) / tau
        if callback is not None:
            callback(a, b)
    return (a + b) / 2.0
```

```python
left_boud_gs = []
right_bound_gs = []
approximation_gs = []

cb_gs = lambda a, b: my_callback(a, b, left_boud_gs, right_bound_gs, approximation_gs)
x_gs = golden_search(f, a, b, epsilon, cb_gs)

print(f(x_opt))
print(f(x_gs))
print(np.abs(x_opt - x_true))
```

    6.93889390875399e-18
    9.549014390504221e-18
    9.313225746154785e-10

### Сравнение методов одномерной минимизации

```python
plt.figure(figsize=(10,6))
plt.semilogy(np.arange(1, len(approximation_bs) + 1), np.abs(x_true - np.array(approximation_bs, dtype=np.float64)), label="Binary search")
plt.semilogy(np.arange(1, len(approximation_gs) + 1), np.abs(x_true - np.array(approximation_gs, dtype=np.float64)), label="Golden search")
plt.xlabel(r"Number of iterations, $k$", fontsize=26)
plt.ylabel("Error rate upper bound", fontsize=26)
plt.legend(loc="best", fontsize=26)
plt.xticks(fontsize = 26)
_ = plt.yticks(fontsize = 26)
```

```python
%timeit binary_search(f, a, b, epsilon)
%timeit golden_search(f, a, b, epsilon)
```

    22.6 µs ± 1.13 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
    84.1 µs ± 6.64 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

## Пример иного поведения методов

$$
f(x) = \sin(\sin(\sin(\sqrt{x}))), \; x \in [2, 60]
$$

```python
f = lambda x: np.sin(np.sin(np.sin(np.sqrt(x))))
x_true = (3 * np.pi / 2)**2
a = 2
b = 60
epsilon = 1e-8
plt.plot(np.linspace(a,b), f(np.linspace(a,b)))
plt.xticks(fontsize = 28)
_ = plt.yticks(fontsize = 28)
```

## Сравнение скорости сходимости и времени работы методов

### Метод дихотомии

```python
left_boud_bs = []
right_bound_bs = []
approximation_bs = []

callback_bs = lambda a, b: my_callback(a, b,
            left_boud_bs, right_bound_bs, approximation_bs)

x_opt = binary_search(f, a, b, epsilon, callback_bs)
print(np.abs(x_opt - x_true))
```

    2.1968899233115735e-07

### Метод золотого сечения

```python
left_boud_gs = []
right_bound_gs = []
approximation_gs = []

cb_gs = lambda a, b: my_callback(a, b, left_boud_gs, right_bound_gs, approximation_gs)
x_gs = golden_search(f, a, b, epsilon, cb_gs)

print(np.abs(x_opt - x_true))
```

    2.1968899233115735e-07

### Сходимость

```python
plt.figure(figsize=(8,6))
plt.semilogy(np.abs(x_true - np.array(approximation_bs, dtype=np.float64)), label="Binary")
plt.semilogy(np.abs(x_true - np.array(approximation_gs, dtype=np.float64)), label="Golden")
plt.legend(fontsize=28)
plt.xticks(fontsize=28)
_ = plt.yticks(fontsize=28)
plt.xlabel(r"Number of iterations, $k$", fontsize=26)
plt.ylabel("Error rate upper bound", fontsize=26)
```

### Время работы

```python
%timeit binary_search(f, a, b, epsilon)
%timeit golden_search(f, a, b, epsilon)
```

    455 µs ± 35.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    416 µs ± 26.2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

## Резюме
1. Введение в численные методы оптимизации
2. Общая схема работы метода
3. Способы сравнения методов оптимизации
4. Зоопарк задач и методов
5. Одномерная минимизация


```python
import os
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['mathtext.fontset'] = 'stix'
```

Load 200ns Aib9 trajectory

```python
infile = '../../DATA/Train/AIB9/sum_phi_200ns.npy'
input_x = np.load(infile)

bins=np.arange(-15., 17, 1)
num_bins=len(bins)
idx_200ns=np.digitize(input_x, bins)

di=1
N_mean=np.sum(np.abs(idx_200ns[:-di]-idx_200ns[di:])==1)
N_mean/=len(idx_200ns)

N0=len(np.where(idx_200ns<=15)[0])
N1=len(np.where(idx_200ns>=16)[0])
kappa_in = N0/N1

print('kappa:', kappa_in)
print('Nearest neighbor:', N_mean)
```

    kappa: 1.148993438587424
    Nearest neighbor: 0.39906640023339995

Check 100ns Aib9 trajectory

```python
idx_100ns = idx_200ns[:2000000]

di=1
N_mean=np.sum(np.abs(idx_100ns[:-di]-idx_100ns[di:])==1)
N_mean/=len(idx_100ns)

N0=len(np.where(idx_100ns<=15)[0])
N1=len(np.where(idx_100ns>=16)[0])
kappa_in = N0/N1

print('kappa:', kappa_in)
print('Nearest neighbor:', N_mean)
```

    kappa: 1.0115786466903496
    Nearest neighbor: 0.397483

# Calculate Nearest neighbor $\langle N\rangle$ sampled from the first training

In the first training, we let 800 independent LSTMs predict 800 trajectories of 100$ns$. Since we are using LSTM as a generative model, we can also train just one LSTM and use it to generate 800 predictions, starting from either the same initial condition or different initial conditions.

Data location: `./Output/`

```python
N_mean_list=[]
output_dir='./Output'
for i in range(800):

    pred_dir=os.path.join(output_dir, '{}/prediction.npy'.format(i))
    prediction=np.load(pred_dir)

    di=1
    N_mean=np.sum(np.abs(prediction[:-di]-prediction[di:])==1)
    N_mean/=len(prediction)
    N_mean_list.append(N_mean)

N_mean_arr=np.array(N_mean_list)
```

Plot distribution

```python
hist = np.histogram( N_mean_arr, bins=50 )
prob = hist[0].T
mids = 0.5*(hist[1][1:]+hist[1][:-1])

fig, ax = plt.subplots(figsize=(5,4))

ax.set_title('Distribution', size=20)
ax.plot(mids, prob)

ax.tick_params(axis='both', which='both', direction='in', labelsize=14)
ax.set_xlabel('$\langle N\\rangle$', size=16)
ax.set_ylabel('Counts', size=16)

plt.show()
```

# Determine $\Delta\lambda$

Following the reference, we want to solve the following equation for $\Delta\lambda$

\begin{align}
    \bar{s}^{(j)}_2&=\sum_{\Gamma}P^{(2)}_{\Gamma}s^{(j)}_{\Gamma} \nonumber \\
    &=\frac{\sum_{k\in\Omega} s^{(j)}_k e^{-\Delta\lambda_j s^{(j)}_k} }{\sum_{k\in\Omega} e^{-\Delta\lambda_j s^{(j)}_k}} \\
    &=f(\Delta\lambda)
    \label{eq:lambda_solver}
\end{align}

To determine the $\Delta\lambda$ value, we can calculate the above equation and plot it versus $\Delta\lambda$, and find $\Delta\lambda=\Delta\lambda_{\ast}$ which gives
\begin{align}
\bar{s}^{(j)}_2=f(\Delta\lambda_{\ast})=s^{\rm target}
\end{align}

We would also like to predict the variance of $\bar{s}^{(j)}_2$, which can be obtained through derivative of $f$.
\begin{align}
f'(\lambda)=-\sigma^2_{\bar{s}^{(j)}_2}
\end{align}

### $s = \langle N\rangle$

```python
def f(lm):
    return np.sum(N_mean_arr*np.exp(-lm*N_mean_arr))/np.sum(np.exp(-lm*N_mean_arr))

def df(lm):
    return f(lm)**2-np.sum(N_mean_arr*N_mean_arr*np.exp(-lm*N_mean_arr))/np.sum(np.exp(-lm*N_mean_arr))

lm_arr = np.linspace(0,1000)
f_arr = [f(lm_i) for lm_i in lm_arr]

fig, ax=plt.subplots(figsize=(5,3))

ax.plot(lm_arr, f_arr, label='$f$')

ax.tick_params(axis='both', which='both', direction='in', labelsize=14)
ax.set_xlabel('$\lambda$', size=16)
ax.set_ylabel('$f(\lambda)$', size=16)

ax.legend(fontsize=16)

plt.show()

lm=62
print( 'f({:.1f}) = {:.6f}'.format(lm, f(lm)) )
print( 'Standard error stderr[f(0)]={:.6f}'.format(np.std(N_mean_arr)/np.sqrt(len(N_mean_arr))) )

print( 'df({:.1f}) = {:.6f}'.format(lm, df(lm)) )
print( 'Expected standard error for new N_mean = {:.6f}'.format( np.sqrt(-df(lm))/np.sqrt(10) ) )
```

Let's see if select 10 predictions to build the subset is enough.

```python
lm_ast=62
p=np.exp(-lm_ast*(N_mean_arr))
p/=np.sum(p)

subset_mean_arr = []
subset_stdv_arr = []
for i in range(200):
    idx = np.random.choice(len(N_mean_arr), 10, p=p)
    selected = N_mean_arr[idx]

    mean=np.mean(selected)
    stdv=np.std(selected)/np.sqrt(len(selected))

    subset_mean_arr.append(mean)
    subset_stdv_arr.append(stdv)

fig, ax = plt.subplots(figsize=(12,5), nrows=1, ncols=2)

ax[0].plot(subset_mean_arr)
ax[0].plot(np.arange(len(subset_mean_arr)), [0.38]*len(subset_mean_arr))

ax[1].plot(subset_stdv_arr)
ax[1].plot(np.arange(len(subset_stdv_arr)), [0.004]*len(subset_stdv_arr))

ax[0].tick_params(axis='both', which='both', direction='in', labelsize=16)
ax[0].set_xlabel('indices', size=16)
ax[0].set_ylabel('$\langle N\\rangle$', size=16)
ax[0].set_ylim(0.3,0.5)

ax[1].tick_params(axis='both', which='both', direction='in', labelsize=16)
ax[1].set_xlabel('indices', size=16)
ax[1].set_ylabel('$\sigma_{N}$', size=16)
ax[1].set_ylim(0.0,0.01)

plt.show()
```

So we will constrain our $\langle N\rangle$ to 0.38 with standard error 0.0041. Although we have shown above that subset size=10 is sufficient, there could be variance in mean constraint. Therefore, we will also constrain our sampling until we reach a reasonable value of $\langle N\rangle$ of the subset.

```python
lm_ast=62
p=np.exp(-lm_ast*(N_mean_arr))
p/=np.sum(p)

mean=np.inf
stdv=np.inf
while abs(mean-0.380)>0.001 or abs(stdv-0.0041)>0.0001:
    idx = np.random.choice(len(N_mean_arr), 10, p=p)
    selected = N_mean_arr[idx]

    mean=np.mean(selected)
    stdv=np.std(selected)/np.sqrt(len(selected))

print( 'mean of selected sample = {:.3f}'.format(np.mean(selected)) )
print( 'Standard error stderr[selected sample] = {:.3f}'.format(np.std(selected)/np.sqrt(len(selected))) )

for ki in kappa_arr[idx]:
    print('{:.3f}'.format(ki))
```

    mean of selected sample = 0.380
    Standard error stderr[selected sample] = 0.004
    0.488
    0.270
    1.270
    0.078
    0.099
    7.103
    0.495
    5.780
    3.174
    3.875

# Concatenate subset as a new training set

Concatenate the subset to a single trajectory, this concatenated trajectory is then used later to re-train a new LSTM.

```python
conc=[]
output_dir='./Output'

for i in idx:
    pred_dir=os.path.join(output_dir, '{}/prediction.npy'.format(i))
    prediction=np.load(pred_dir)
    conc.extend(prediction)

conc = np.array(conc)
```

We can also check what the $\langle N\rangle$ as well as $\kappa$ value of concatenated trajectory is.

```python
N0=len(np.where(conc<=15)[0])
N1=len(np.where(conc>=16)[0])
kappa_conc = N0/N1

di=1
N_mean_conc=np.sum(np.abs(conc[:-di]-conc[di:])==1)
N_mean_conc/=len(conc)

print('kappa:', kappa_conc)
print('Nearest neighbor:', N_mean_conc)
```

    kappa: 0.9525129329575397
    Nearest neighbor: 0.37985445


# Quantum Advantage For Hubbard Model On a Near-Term QuantumComputer
Ground state calculations using classical methods require the storage of three vectors of the size of the number of states. The number of states scales exponentially with the size of the system, which limits the number of sites in the lattice to about $20\times 20$ on currently available classical hardware. For low temperatures, convergence problems appear that lead to an exponential growth of computational effort with decreasing temperature due to the so-called fermion sign problem.

For the Hubbard-type problems we use quantum simulation to map the system Hamiltonian on qubits system (quantum computer) and run quantum algorithms on them to demonstrate trend of advantage that quantum computers promise. The advantage is expected to be exponential as a quantum computer is able to store exponential amount of information on polynomial number of qubits.

be  exponential  as  a  quantum  computer  is  able  to  store  exponentialamount of information on polynomial number of qubits.

### Hubbard model
The Hubbard model [1] is an approximate model used, especially in solid-state physics, to describe the transition between conducting and insulating systems. The Hubbard model is the simplest model of interacting particles in a lattice, with only two terms in the Hamiltonian: a kinetic term allowing for tunneling ("hopping") of particles between sites of the lattice and a potential term consisting of an on-site interaction. The Hubbard model is also used as a model for high-temperature superconductivity as well as to describe the behavior of ultracold atoms trapped in optical lattices. For electrons in a solid, the Hubbard model can be considered as an improvement on the tight-binding model, which includes only the hopping term. For strong interactions, it can give qualitatively different behavior from the tight-binding model, and correctly predicts the existence of so-called Mott insulators, which are prevented from becoming conducting by the strong repulsion between the particles.

The fact that the Hubbard model has not been solved analytically in arbitrary dimensions has led to intense research into numerical methods for these strongly correlated electron systems. One major goal of this research is to determine the low-temperature phase diagram of this model, particularly in two-dimensions.

It is known that quantum many-body problems require exponential time, in the size of the problem,
to solve on a classical computer without approximations. As Feynman envisioned, one could use a quantum computer to simulate quantum systems to overcome such difficulties [2,3].
With quantum computers one can prepare initial states of the system under study as well as have
control over the Hamiltonians that governs the time evolutions of those states.
Recent advances in quantum-computing hardware brought us quite close to Feynman’s vision.
Developing optimized simulation algorithms is of highest importance as current near-term quantum
hardwares have limitations in scaling, and errors in state preparation, qubit-qubit couplings, gate
sequences, and readout. Significant progress has been made in this direction [4-7].
Several quantum algorithms exist to simulate correlated fermions on linear and 2D qubit arrays with
nearest-neighbor couplings which are typical for superconducting transmon qubits.
We will use the Fermi-Hubbard model [1] to demonstrate these algorithms. The Hubbard model
approximates the long-range Coulomb interaction of electrons in a crystal with a local on-site
interaction. This locality reduces the resources required for simulating the model and makes it a
prime candidate for the early applications of quantum simulations [8].
For a lattice with only one type of site and edges from each site only to itself and its neighbors, the Hamiltonian for the spinful model has the form

\begin{eqnarray}
        H &=& - \sum_{a < b} t_{a, b}^{(\mathrm{onsite})}
               \sum_{i} \sum_{\sigma}
                     (a^\dagger_{i, a, \sigma} a_{i, b, \sigma} +
                      a^\dagger_{i, b, \sigma} a_{i, a, \sigma})\nonumber
            \\&-& \sum_{a} t_{a, a}^{(\mathrm{nghbr})}
               \sum_{\{i, j\}} \sum_{\sigma}
                     (a^\dagger_{i, a, \sigma} a_{j, a, \sigma} +
                      a^\dagger_{j, a, \sigma} a_{i, a, \sigma})\nonumber
            \\ &-& \sum_{a < b} t_{a, b}^{(\mathrm{nghbr})}
               \sum_{(i, j)} \sum_{\sigma}
                     (a^\dagger_{i, a, \sigma} a_{j, b, \sigma} +
                      a^\dagger_{j, b, \sigma} a_{i, a, \sigma})\nonumber
            + \sum_{a < b} U_{a, b}^{(\mathrm{onsite}, +)}
               \sum_{i} \sum_{\sigma}
                     n_{i, a, \sigma} n_{i, b, \sigma}\nonumber
            \\&+& \sum_{a} U_{a, a}^{(\mathrm{nghbr}, +)}
               \sum_{\{i, j\}} \sum_{\sigma}
                     n_{i, a, \sigma} n_{j, a, \sigma}
             + \sum_{a < b} U_{a, b}^{(\mathrm{nghbr}, +)}
               \sum_{(i, j)} \sum_{\sigma}
                     n_{i, a, \sigma} n_{j, b, \sigma}\nonumber
            \\&+& \sum_{a \leq b} U_{a, b}^{(\mathrm{onsite}, -)}
               \sum_{i} \sum_{\sigma}
                     n_{i, a, \sigma} n_{i, b, -\sigma}\nonumber
            +
            \sum_{a} U_{a, a}^{(\mathrm{nghbr}, -)}
               \sum_{\{ i, j \}} \sum_{\sigma}
                     n_{i, a, \sigma} n_{j, a, -\sigma}
            \\&+& \sum_{a < b} U_{a, b}^{(\mathrm{nghbr}, -)}
               \sum_{( i, j )} \sum_{\sigma}
                     n_{i, a, \sigma} n_{j, b, -\sigma}\nonumber
            \\&-& \sum_{a} \mu_a
               \sum_i \sum_{\sigma} n_{i, a, \sigma}
            - h \sum_{i} \sum_{a}
                \left(n_{i, a, \uparrow} - n_{i, a, \downarrow}\right) \qquad (1)
        \end{eqnarray}

The tunneling parameter corresponds to the terms
$$
t \sum_{(i, j) \in E^{(\mathrm{edge type})}}
            \sum_{\sigma}
            \left(a_{i, a, \sigma}^{\dagger} a_{j, b, \sigma}
            + a_{j, b, \sigma}^{\dagger} a_{i, a, \sigma}\right)
$$
One can also construct the Hamiltonian for the spinless model, which հas the form

\begin{eqnarray}
            H = &-& \sum_{a < b} t_{a, b}^{(\mathrm{onsite})}
               \sum_{i}
                     (a^\dagger_{i, a} a_{i, b} +
                      a^\dagger_{i, b} a
                      a_{i, a}) -
             \sum_{a} t_{a, a}^{(\mathrm{nghbr})}
               \sum_{\{i, j\}}
                     (a^\dagger_{i, a} a_{j, a} +
                      a^\dagger_{j, a} a_{i, a})
             \\&-&
             \sum_{a < b} t_{a, b}^{(\mathrm{nghbr})}
               \sum_{(i, j)}
                     (a^\dagger_{i, a} a_{j, b} +
                      a^\dagger_{j, b} a_{i, a})
            +
             \sum_{a < b} U_{a, b}^{(\mathrm{onsite})}
               \sum_{i}
                     n_{i, a} n_{i, b}
            \\
            &+& \sum_{a} U_{a, a}^{(\mathrm{nghbr})}
               \sum_{\{i, j\}}
                     n_{i, a} n_{j, a}
             + \sum_{a < b} U_{a, b}^{(\mathrm{nghbr})}
               \sum_{(i, j)}
                     n_{i, a} n_{j, b}\nonumber
            - \sum_{a} \mu_a
               \sum_i n_{i, a}
\end{eqnarray}

The tunneling parameter corresponds to the terms
$$
-t \sum_{(i, j) \in E^{(\mathrm{edge type})}}
            \left(a_{i, a}^{\dagger} a_{j, b}
            + a_{j, b}^{\dagger} a_{i, a}\right)$$

### Fermi-Hubbard Model

The Fermi-Hubbard Hamiltonian for the spinful model has the form

\begin{eqnarray}
        H = &-& t \sum_{\langle i,j \rangle} \sum_{\sigma}
                     (a^\dagger_{i, \sigma} a_{j, \sigma} +
                      a^\dagger_{j, \sigma} a_{i, \sigma})
             + U \sum_{i} a^\dagger_{i, \uparrow} a_{i, \uparrow}
                         a^\dagger_{i, \downarrow} a_{i, \downarrow}\nonumber
            \\
            &-& \mu \sum_i \sum_{\sigma} a^\dagger_{i, \sigma} a_{i, \sigma}
             - h \sum_i (a^\dagger_{i, \uparrow} a_{i, \uparrow} -
                       a^\dagger_{i, \downarrow} a_{i, \downarrow})
\end{eqnarray}

The single-band Fermi-Hubbard model, which is a radical simplification of the full electronic structure Hamiltonian, is described by the following Hamiltonian

\begin{eqnarray}\label{1}
    H_{FH} = &-&\sum_{\langle j,k\rangle,\sigma}
    t_{j,k}(a_{j,\sigma}^\dagger a_{k,\sigma}+ \mathrm{h.c.}) + U \sum_j n_{j,\uparrow}n_{j,\downarrow}\nonumber\\&+&
    \sum_{j,\sigma}(\varepsilon_j-\mu)n_j-
    \sum_j h_j(n_{j,\uparrow} -n_{j,\downarrow})
\end{eqnarray}
where $a_{j,\sigma}^\dagger (a_{j,\sigma})$
is the creation (annihilation) operator for the $j$th site with spin $\sigma$
and $n_{j,\sigma} = a_{j,\sigma}^\dagger a_{j,\sigma}$ is the fermion occupation number operator and $n_j = \sum_\sigma n_{j,\sigma}$. From all the
electronic bands, this model keeps only a single orbital close to the Fermi energy and reduces the
long-range Coulomb repulsion to just the local repulsion $U$ within this orbital. The first term on the right side of Eq. (1) describes fermions hopping between sites, the second term describes the on-site interactions, and the remaining two terms describe a local potential and a magnetic field. One can consider a translationally invariant model and then drop the indices in $t_{j,k}$
and $\varepsilon_j$ since all sites are
equivalent.

### Bose-Hubbard model
The Hamiltonian for the Bose-Hubbard model has the form

$$H = - t \sum_{\langle i, j \rangle} (b_i^\dagger b_j + b_j^\dagger b_i)
         + V \sum_{\langle i, j \rangle} b_i^\dagger b_i b_j^\dagger b_j
         + \frac{U}{2} \sum_i b_i^\dagger b_i (b_i^\dagger b_i - 1)
         - \mu \sum_i b_i^\dagger b_i.
$$

### Complexity

The computational complexity of the Hubbard model is reduced compared to the full electronic
structure problem, since only a single orbital is used per unit cell instead of dozens needed for the
full problem, and the number of terms is linear in $N$ instead of scaling with the fourth power $N^4$.
On a lattice of $20\times 20$ unit cells this means a reduction of the total number of terms from about $10^{12}$ to $10^{3}$, which makes such a simulation feasible on a quantum computer.

The one-dimensional (1D) Hubbard model was solved by \cite{Lieb}, however, it remains an open question for a full theoretical analysis of the 2D Hubbard model which requires going beyond the validity of
mean-field and perturbation theory arguments.
%The phase diagram of the 2D Fermi-Hubbard model is depicted in the picture below.
%The critical $T_c$ temperature depends on the value of hole doping, AF stands for antiferromagnet.
To characterize the phases one can compute key quantities such as the quasiparticle energy gap and phase stiffness (superfluid density) in a superconducting phase. To answer these questions, one needs to determine the ground-state wave function for a range of interaction parameters and densities and then measure
ground-state correlation functions.

At half-filling, which is the case of one electron per unit cell, the Hubbard model gives a simple account of Mott insulating behavior. For $U=0$, the ground state is metallic. There is a single band
and it is half filled, the Fermi surface is $|k_x \pm k_y| = \pi/a$, where $a$ is the lattice constant. However,
for $U>0$, the ground state is insulating. The physics is different in the $U \ll t$
and $U \gg t$ limits.
For $U\ll t$, the system lowers its energy by developing antiferromagnetic order, as a result of which the unit cell doubles in size. If there are two electrons per unit cell the system is effectively a band
insulator. If the Hubbard model captures the physics of the cuprate superconductors, then
superconducting order with $d_{x_2-y_2}$
pairing symmetry must occur for a density of $1-x$ electrons per unit cell, with in the range $0.05 < x < 0.25$. If the Hubbard model is not superconducting in
this doping range, then superconductivity in the cuprates must be due to effects that are missing in
the Hubbard model, such as longer-ranged interactions, longer-ranged hopping, interlayer coupling,
and phonons.

Exact numerical diagonalization of Hubbard model is limited to about 20 sites (40 logical qubits)
[10, 11], which is too small for a finite-size scaling analysis. Classical approximate methods such as
quantum Monte Carlo simulations or many-body theory expansions have been used to simulate
systems with hundreds of sites, which allows for extrapolation to the thermodynamic limit. Monte
Carlo methods suffer from sign problems which prevent them from simulating systems at very low
temperatures [13]. Density matrix renormalization group (DMRG) [14] applied to the 2D Hubbard
model requires mapping to an effective 1D problem. Other numerical methods to determine the
phase diagrams of the Hubbard model include the dynamical cluster approximation [15, 16] and the
density matrix embedding theory [10, 17]. These methods can asymptotically approach the exact
solution with increasingly larger clusters which requires an exponential amount of computing resources on a classical computer.

On a quantum computer, simulating the Fermi-Hubbard model, one maps the fermionic operators to qubit operators. In the second quantization picture, a particular spin orbital being unoccupied(occupied) is represented by the qubit state $|0\rangle (|1\rangle)$. To account for the parities of qubits
corresponding to other spin orbitals, e.g., by using the Jordan-Wigner transformation (JWT) [18, 27], fermionic anticommutation relations must be satisfied.
The last two terms of Eq. (1) can be implemented using single qubit operators, the on-site
interaction term can be implemented with two-qubit interactions. Because of the nonlocal parity
operators in the JWT, the hopping terms cannot be implemented in more than one spatial
dimension. It is of practical importance to reduce the depth of the quantum circuits for these terms
with only local qubit interactions [7]. Several quantum algorithms to simulate fermionic systems on
near-term devices with nearest-neighbor qubit-qubit couplings has been reported recently [20]. This
opens a possibility to simulate many body physics, including the Fermi-Hubbard model, in the noisy
intermediate-scale quantum era [21].

### Mapping to code

Let's build the code structure and show the approach, where we describe the transition between conducting and insulating systems.
We now give the solving approach of a restricted case of Fermi-Hubbard model.

By exploiting the open source OpenFermion library [22] one can prepare an arbitrary Slater determinant and map fermions to qubits. First, we describe the interface between OpenFermion and Rigetti’s quantum simulation environment called Forest. The interface provides a method of transforming data generated in OpenFermion to a similar representation
in pyQuil [24]. For this example we use OpenFermion to build a four-site single-band periodic boundary Hubbard model and apply first-order Trotter time-evolution to a starting state of two localized electrons of opposite spin.
The Forest-OpenFermion plugin provides the routines to inter-convert between the OpenFermion `QubitOperator` data structure and the synonymous data structure in pyQuil called a `PauliSum`.

The `FermionOperator` in OpenFermion can be used to translate the mathematical expression of the Hamiltonian directly to executable code. While we show how this model can be built in one line using the OpenFermion hamiltonians module, here we take the opportunity to demonstrate the ease of creating such models for study that could be
easily modified as desired. Given the Hamiltonian of the Hubbard system

\begin{equation}
    H_{FH} = - \sum_{\langle j,k\rangle,\sigma}
    t_{j,k}(a_{j,\sigma}^\dagger a_{k,\sigma}+ \mathrm{h.c.}) + 4 \sum_j n_{j,\uparrow}n_{j,\downarrow},
\end{equation}

where we first consider the problem without local potential and magnetic field. We also used $U=4$ for numerical procedures. The example code to build this Hamiltonian is as follows

```python
from openfermion.transforms import jordan_wigner
from openfermion.ops import FermionOperator
from openfermion.ops import hermitian_conjugated

hubbard_hamiltonian = FermionOperator()
spatial_orbitals = 4
for i in range(spatial_orbitals):
    electron_hop_alpha = FermionOperator(((2*i, 1),
            (2*((i+1) % spatialorbitals), 0)))
    electron_hop_beta = FermionOperator(((2*i+1, 1),
            ((2*((i+1) % spatial_orbitals) + 1 ),0)))
    hubbard_hamiltonian +=-1*(electron_hop_alpha +
            hermitian_conjugated(electron_hop_alpha))
    hubbard_hamiltonian += -1*(electron_hop_beta +
            hermitian_conjugated(electron_hop_beta))
    hubbard_hamiltonian += FermionOperator (((2*i, 1),
            (2*i, 0), (2*i+1, 1), (2*i+1, 0)), 4.0)
```

Here, we have implicitly used even indexes [0, 2, 4, 6] as $\alpha$ spin-orbitals and odd indexes [1, 3, 5, 7] as $\beta$
spin-orbitals.

The same model can be built using the OpenFermion Hubbard model builder routine in the hamiltonians module with a single function call:

```python
from openfermion.hamiltoniansi mport fermi_hubbard

x_dim = 4
y_dim = 1
periodic = True
chemical_potential = 0
tunneling = 1.0
coulomb = 4.0

of_hubbard_hamiltonian = fermi_hubbard(x_dim, y_dim, tunneling,
            coulomb, chemical_potential=None, spinless=False)
```
The above variables are self-explanatory.

Using the Jordan-Wigner transform functionality of OpenFermion, the Hubbard Hamiltonian can be transformed to a sum of QubitOperators which are then transformed to pyQuil PauliSum objects using routines in the Forest-OpenFermion plugin imported earlier.

```python
hubbard_term_generator = jordan_wigner(hubbard_hamiltonian)
pyquil_hubbard_generator = qubitop_to_pyquilpauli(hubbard_term_generator)
```

Now, with the data successfully transformed to a pyQuil representation, one can use them to construct quantum circuits on rigetti's simulator or hardware and make experiments.

Often, one would like to obtain a sparse matrix representation of an operator which can be analyzed numerically. There is code in both openfermion.transforms and openfermion.utils which facilitates this. The function get\_sparse\_operator converts either a FermionOperator, a QubitOperator or other more advanced classes such as InteractionOperator to a scipy.sparse.csc matrix. There are numerous functions in openfermion.utils which one can call on the sparse operators such as "get\_gap", "get\_hartree\_fock\_state", "get\_ground\_state", etc. We show this off by computing the ground state energy of the Hubbard model. To do that, we use code from the openfermion.hamiltonians module which constructs lattice models of fermions such as Hubbard models.

```python
from openfermion.hamiltonians import fermi_hubbard
from openfermion.transforms import get_sparse_operator
from openfermion.transforms import jordan_wigner
from openfermion.utils import get_ground_state

# Set model.
x_dim = 2
y_dim = 2
tunneling = 2.
coulomb = 1.
magnetic_field = 0.5
chemical_potential = 0.25
periodic = 1
spinless = 1

# Get fermion operator.
hubbard_model = fermi_hubbard(
    x_dimension, y_dimension, tunneling, coulomb,
    chemical_potential, magnetic_field, periodic, spinless)

# Get qubit operator under Jordan-Wigner.
jw_hamiltonian = jordan_wigner(hubbard_model)
jw_hamiltonian.compress()

# Get scipy.sparse.csc representation.
sparse_operator = get_sparse_operator(hubbard_model)

print('\nEnergy of the model is {}'.format(
    get_ground_state(sparse_operator)[0]))
```

### Measuring an observable

To measure observables or correlation functions on a quantum computer we use Paili group expansion of the related operator.
\begin{equation}
    O = \sum_{i=1}^n \alpha_i P_i,
\end{equation}
where $n$ is the basis dimension needed for expansion and $P_i \in \{I, \sigma_x, \sigma_y, \sigma_z\}^{\otimes N}$ is a tensor product of single-qubit Pauli operators on $N$ qubits.
This is needed as quantum circuits are natural for sampling in Pauli basis.
Then the expectation value of the observable $O$ is
\begin{equation}
    \langle O\rangle = \sum_{i=1}^n \alpha_i \langle P_i\rangle,
\end{equation}

For constructing Slater determinants to approximate the ground state, we use the variational quantum eigensolver algorithm (VQE), where the ansatz state is prepared via a parametrized quantum circuit and optimized for minimum energy. The advantage of VQE over classical simulation methods is that it can prepare trial states that are not amenable to efficient classical numerics. We have described the VQE algorithm in [[26]](https://github.com/gate42qc/Tutorials/blob/master/VQE/VQEforhydrogenmoleculeenergy.ipynb).

So let's prepare the necessary packages and start modeling the problem

```python
from openfermion.hamiltonians import fermi_hubbard
from openfermion.transforms import get_fermion_operator, jordan_wigner
from openfermion.utils import get_ground_state
from openfermion.ops import QubitOperator
from forestopenfermion import pyquilpauli_to_qubitop
from forestopenfermion import qubitop_to_pyquilpauli

import numpy as np
import random
import matplotlib.pyplot as plt
%matplotlib inline

from pyquil.quil import Program
from pyquil.gates import *
from pyquil.paulis import exponentiate
from pyquil.quil import Program
import pyquil.api as api
# we also import a function from module random_circuit we created
from random_circuit import get_ansatz_circuit_generator
```

```python
# Setting the model
x_dimension = 3
y_dimension = 2
tunneling = 2.
coulomb = 1.0
magnetic_field = 0.5
chemical_potential = 0.25
periodic = 1
spinless = 0

# create range of parameters for scan to model different material specifics
# change to desired values
tunnelings = np.arange(0.5, 1.5, 0.02)
coulombs = np.arange(0, 1.5, 0.05)
magnetic_fields = np.arange(0.5, 20, 2)
chemical_potentials = np.arange(-500., 500, 50)

# Get fermion operators
hubbard_tunnels = [fermi_hubbard(x_dimension, y_dimension, tunneling, coulomb,
                                 chemical_potential, magnetic_field, periodic, spinless)
                  for tunneling in tunnelings]

hubbard_coulombs = [fermi_hubbard(x_dimension, y_dimension, tunneling, coul,
                                  chemical_potential, magnetic_field, periodic, spinless)
                  for coul in coulombs]

hubbard_magn = [fermi_hubbard(x_dimension, y_dimension, tunneling, coulomb,
                              chemical_potential, magnetic_field, periodic, spinless)
                  for magn in magnetic_fields]

hubbard_chem = [fermi_hubbard(x_dimension, y_dimension, tunneling, coulomb,
                              chemical_potential, magnetic_field, periodic, spinless)
                  for chem in chemical_potentials]
```

```python
coulombs
```

    array([0.  , 0.05, 0.1 , 0.15, 0.2 , 0.25, 0.3 , 0.35, 0.4 , 0.45, 0.5 ,
           0.55, 0.6 , 0.65, 0.7 , 0.75, 0.8 , 0.85, 0.9 , 0.95, 1.  , 1.05,
           1.1 , 1.15, 1.2 , 1.25, 1.3 , 1.35, 1.4 , 1.45])

```python
# Get qubit operator Hubbard hamiltonian under Jordan-Wigner transformation.

jw_ham_tunnels = [jordan_wigner(hubbard_model) for hubbard_model in hubbard_tunnels]
jw_ham_coulombs = [jordan_wigner(hubbard_model) for hubbard_model in hubbard_coulombs]
jw_ham_magn = [jordan_wigner(hubbard_model) for hubbard_model in hubbard_magn]
jw_ham_chem = [jordan_wigner(hubbard_model) for hubbard_model in hubbard_chem]

#pyquil_hubbard_generator = qubitop_to_pyquilpauli(jw_hamiltonian)
jw_pyquil_hubbard_tunnels = [qubitop_to_pyquilpauli(jw_hamiltonian) for jw_hamiltonian in jw_ham_tunnels]
jw_pyquil_hubbard_coulombs = [qubitop_to_pyquilpauli(jw_hamiltonian) for jw_hamiltonian in jw_ham_coulombs]
jw_pyquil_hubbard_magn = [qubitop_to_pyquilpauli(jw_hamiltonian) for jw_hamiltonian in jw_ham_magn]
jw_pyquil_hubbard_chem = [qubitop_to_pyquilpauli(jw_hamiltonian) for jw_hamiltonian in jw_ham_chem]
```

### Ground state preparation using VQE
For usage of VQE see [our tutorial](https://github.com/gate42qc/Tutorials/blob/master/VQE/VQE_for_hydrogen_molecule_energy.ipynb)

```python
# depth of the ansatz circuit
d = 1
# number of qubits are defined by lattice dimensions
qubits = list(range(2* x_dimension * y_dimension))
number_of_qubits = len(qubits)
```

```python

variational_state_evolve = get_ansatz_circuit_generator(qubits, d)

```

```python
thetas = np.random.rand(number_of_qubits*(d+1))*np.pi

```

```python
# Let's see the first Hubbard hamiltonian represented as a Pauli expansion
print(jw_pyquil_hubbard_tunnels[1])
```

    (-0.26+0j)*Y0*Z1*Y2 + (-0.26+0j)*X0*Z1*X2 + (-0.26+0j)*Y1*Z2*Y3 + (-0.26+0j)*X1*Z2*X3 + (-0.26+0j)*Y0*Z1*Z2*Z3*Z4*Z5*Y6 + (-0.26+0j)*X0*Z1*Z2*Z3*Z4*Z5*X6 + (-0.26+0j)*Y1*Z2*Z3*Z4*Z5*Z6*Y7 + (-0.26+0j)*X1*Z2*Z3*Z4*Z5*Z6*X7 + (-0.375+0j)*Z1 + (0.125+0j)*Z0 + (0.25+0j)*Z0*Z1 + (-0.26+0j)*Y2*Z3*Y4 + (-0.26+0j)*X2*Z3*X4 + (-0.26+0j)*Y3*Z4*Y5 + (-0.26+0j)*X3*Z4*X5 + (-0.26+0j)*Y2*Z3*Z4*Z5*Z6*Z7*Y8 + (-0.26+0j)*X2*Z3*Z4*Z5*Z6*Z7*X8 + (-0.26+0j)*Y3*Z4*Z5*Z6*Z7*Z8*Y9 + (-0.26+0j)*X3*Z4*Z5*Z6*Z7*Z8*X9 + (-0.375+0j)*Z3 + (0.125+0j)*Z2 + (0.25+0j)*Z2*Z3 + (-0.26+0j)*X0*Z1*Z2*Z3*X4 + (-0.26+0j)*Y0*Z1*Z2*Z3*Y4 + (-0.26+0j)*X1*Z2*Z3*Z4*X5 + (-0.26+0j)*Y1*Z2*Z3*Z4*Y5 + (-0.26+0j)*Y4*Z5*Z6*Z7*Z8*Z9*Y10 + (-0.26+0j)*X4*Z5*Z6*Z7*Z8*Z9*X10 + (-0.26+0j)*Y5*Z6*Z7*Z8*Z9*Z10*Y11 + (-0.26+0j)*X5*Z6*Z7*Z8*Z9*Z10*X11 + (-0.375+0j)*Z5 + (0.125+0j)*Z4 + (0.25+0j)*Z4*Z5 + (-0.26+0j)*Y6*Z7*Y8 + (-0.26+0j)*X6*Z7*X8 + (-0.26+0j)*Y7*Z8*Y9 + (-0.26+0j)*X7*Z8*X9 + (-0.375+0j)*Z7 + (0.125+0j)*Z6 + (0.25+0j)*Z6*Z7 + (-0.26+0j)*Y8*Z9*Y10 + (-0.26+0j)*X8*Z9*X10 + (-0.26+0j)*Y9*Z10*Y11 + (-0.26+0j)*X9*Z10*X11 + (-0.375+0j)*Z9 + (0.125+0j)*Z8 + (0.25+0j)*Z8*Z9 + (-0.26+0j)*X6*Z7*Z8*Z9*X10 + (-0.26+0j)*Y6*Z7*Z8*Z9*Y10 + (-0.26+0j)*X7*Z8*Z9*Z10*X11 + (-0.26+0j)*Y7*Z8*Z9*Z10*Y11 + (-0.375+0j)*Z11 + (0.125+0j)*Z10 + (0.25+0j)*Z10*Z11

```python
from pyquil.api import get_qc
qc = get_qc("12q-qvm")
```

```python
#from grove.pyvqe.vqe import VQE
from vqe import VQE
from scipy.optimize import minimize
```

```python
initial_parameters = thetas

import time
inst = VQE(minimizer=minimize, minimizer_kwargs={'method': 'COBYLA'}) # 'BFGS'
```

```python
start = time.time()
print("-------------------starting optimization---------------------")

# # the VQE result at each interatomic distance is stored results_wf_sim
coulombs_results_wf_sim = [inst.vqe_run(variational_state_evolve,
                               ham,
                               initial_params=initial_parameters,
                               qc=qc, samples=None)
                  for ham in jw_pyquil_hubbard_coulombs]

end = time.time()
print("optimization done - elapsed time is:")
print((end - start)/60, 'minutes')
```

    -------------------starting optimization---------------------
    optimization done - elapsed time is:
    33.0844568490982 minutes

```python
energies_coul = [res.fun for res in coulombs_results_wf_sim]
energies_coul
```

    [-1.1426890813730481,
     -1.3330018265168322,
     -1.5126914585368993,
     -1.6720591321126526,
     -1.7176992812524543,
     -1.9007224635754165,
     -2.0448595511300165,
     -2.1627945731765847,
     -2.327116903337025,
     -2.412578791711057,
     -2.7363764168681803,
     -2.904921603669198,
     -3.026266834452335,
     -3.1297902726221527,
     -3.2178460715050567,
     -3.3124344873688756,
     -3.3869322998856144,
     -3.4598857315806173,
     -3.510606693961045,
     -3.5602624282936617,
     -3.5968501116056193,
     -3.6272681189294693,
     -3.9906241303561383,
     -3.9996540559853995,
     -3.649124520467428,
     -3.634780693407558,
     -3.610896092727703,
     -3.576449924238313,
     -3.595830848595052,
     -3.478665025105412]

```python
plt.scatter(coulombs, energies_coul, label='Fermi_Hubbard_coulomb')
plt.ylabel('Energy (in units of T and J)')
plt.savefig('images/FH_energy_no_noise.png', format='png', dpi=1000)
plt.show()
```

The above simulation is done algebraically. We can do same using sampling approach.

```python
start = time.time()
print("-------------------starting optimization---------------------")

# the VQE result at each interatomic distance is stored results_wf_sim
results_sampl = [inst.vqe_run(variational_state_evolve,
                               ham,
                               initial_params=initial_parameters,
                               qc=qc, samples=100)
                 for ham in jw_pyquil_hubbard_tunnels]

end = time.time()
print("------------optimization done - elapsed time is:--------------")
print((end - start)/60, 'minutes')
```

```python
energies_sampl = [res.fun for res in results_sampl]
print(energies_sampl)
```

```python
plt.scatter(coulombs, energies_sampl, label='Fermi_Hubbard')
#plt.title('Sampling without noise')
plt.xlabel('tunnelings')
plt.ylabel('Energy (in units of T and J)')
plt.savefig('images/FH_energy_samp_no_noise.png', format='png', dpi=1000)
plt.show()
```

### References
[1]  J. Hubbard, ”Electron Correlations in Narrow Energy Bands”. Proceed-ings of the Royal Society of London.276, 238 (1963)
[2]  R.  P.  Feynman,  “Simulating  physics  with  computers,”  InternationalJournal of Theoretical Physics21, 467 (1982).
[3]  I. Georgescu, et. al., “Quantum simulation,” Reviews of Modern Physics86, 153 (2014).
[4]  M. B. Hastings, et. al., “Improving Quantum Algorithms for QuantumChemistry,” Quantum Info. Comput.15, 1 (2015)
[5]  A. Kandala, et. al., “Hardware-efficient variational quantum eigensolverfor small molecules and quantum magnets,” Nature549, 242 (2017).
[6]  M. Reiher, et. al., “Elucidating reaction mechanisms on quantum com-puters,”  Proceedings  of  the  National  Academy  of  Sciences114,  7555(2017).
[7]  R.  Babbush,  et.  al.,  “Low  Depth  Quantum  Simulation  of  ElectronicStructure,” arXiv:1706.00023 (2017)
[8]  D.  Wecker,  et.  al.,  “Solving  strongly  correlated  electron  models  on  aquantum computer,” Physical Review A92, 062318 (2015)
[9]  E.  H.  Lieb  and  F.  Y.  Wu,  “Absence  of  Mott  transition  in  an  exactsolution of the short-range,  one-band model in one dimension,” Phys.Rev. Lett.20, 1445 (1968).
[10]  B.-X. Zheng and G. K.-L. Chan,  “Ground-state phase diagram of thesquare lattice Hubbard model from density matrix embedding theory,”Physical Review B93, 035126 (2016).
[11]  C. J. Jia, et. al., “Fidelity study of the superconducting phase diagramin the two-dimensional single-band Hubbard model,” Phys. Rev. B84,125113 (2011).
[12]  S.  Yamada,  et.  al.,  “16.447  tflops  and  159-billion-dimensional  exact-diagonalization  for  trapped  fermion-hubbard  model  on  the  earth  sim-ulator,” in Supercomputing,  2005. Proceedings of the ACM/IEEE SC2005 Conference (2005) pp. 44.
[13]  M.  Troyer  and  U.-J.  Wiese,  “Computational  Complexity  and  Funda-mental Limitations to Fermionic Quantum Monte Carlo Simulations,”Physical Review Letters94, 170201 (2005).
[14]  S. R. White, “Density-matrix algorithms for quantum renormalizationgroups,” Physical Review B48, 10345 (1993).
[15]  M. H. Hettler, et. al, “Dynamical cluster approximation:  Nonlocal dy-namics  of  correlated  electron  systems,”  Physical  Review  B61,  12739(2000).
[16]  G.  Kotliar,  et.  al.,  “Electronic  structure  calculations  with  dynamicalmean-field theory,” Reviews of Modern Physics78, 865 (2006).
[17]  G. Knizia and G. K.-L. Chan, “Density Matrix Embedding:  A SimpleAlternative to Dynamical Mean-Field Theory,” Physical Review Letters109, 186404 (2012).
[18]  G. Ortiz, et. al., “Quantum algorithms for fermionic simulations,” Phys-ical Review A64, 022319 (2001).
[19]  J. D. Whitfield, et. al., “Simulation of electronic structure Hamiltoniansusing quantum computers,” Molecular Physics109, 735 (2011).
[20]  Z. Jiang, et. al., “Quantum Algorithms to Simulate Many-Body Physicsof Correlated Fermions”, Physical Review Applied9, 044036 (2018).
[21]  J.  Preskill,   “Quantum  computing  in  the  NISQ  era  and  beyond,”arXiv:1801.00862 (2018).
[22]  J. R. McClean, et. al., ”OpenFermion:  The electronic structure packagefor quantum computers,” arXiv:1710.07629 (2017).
[23]  S.  B.  Bravyi  and  A.  Y.  Kitaev,  “Fermionic  Quantum  Computation,”Annals of Physics298, 210 (2002).
[24]  https://github.com/rigetti/pyquil
[25]  R. S. Smith, M. J. Curtis, and W. J. Zeng, ”A practical quantum in-struction set architecture,” arXiv:1608.03355 (2016).
[26]  https://github.com/gate42qc/Tutorials/blob/master/VQE/VQEforhydrogenmoleculeenergy.ipynb
[27]  J. D. Whitfield, et. al., “Local spin operators for fermion simulations,”Phys. Rev. A94, 030301 (2016).
[28]  I. D. Kivlichan, et. al., “Quantum simulation of electronic structure withlinear depth and connectivity,” arXiv:1711.04789 (2017).
[29]  Arute  et.  al.,  ”Quantum  supremacy  using  a  programmable  supercon-ducting processor”, Nature574, 505 (2019).
[30]  V. Bach, E. H. Lieb, and J. P. Solovej, “Generalized Hartree-Fock theoryand the Hubbard model,” Journal of Statistical Physics76, 3 (1994).