```python
try:
    import openmdao.api as om
    import dymos as dm
except ImportError:
    !python -m pip install openmdao[notebooks]
    !python -m pip install dymos[docs]
    import openmdao.api as om
    import dymos as dm
```

    Collecting openmdao[notebooks]
      Downloading openmdao-3.15.0.tar.gz (5.2 MB)
         |████████████████████████████████| 5.2 MB 34.4 MB/s
    Requirement already satisfied: networkx>=2.0 in /usr/local/lib/python3.7/dist-packages (from openmdao[notebooks]) (2.6.3)
    Requirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from openmdao[notebooks]) (1.19.5)
    Collecting pyDOE2
      Downloading pyDOE2-1.3.0.tar.gz (19 kB)
    Requirement already satisfied: pyparsing in /usr/local/lib/python3.7/dist-packages (from openmdao[notebooks]) (3.0.6)
    Requirement already satisfied: scipy in /usr/local/lib/python3.7/dist-packages (from openmdao[notebooks]) (1.4.1)
    Requirement already satisfied: requests in /usr/local/lib/python3.7/dist-packages (from openmdao[notebooks]) (2.23.0)
    Requirement already satisfied: notebook in /usr/local/lib/python3.7/dist-packages (from openmdao[notebooks]) (5.3.1)
    Requirement already satisfied: tabulate in /usr/local/lib/python3.7/dist-packages (from openmdao[notebooks]) (0.8.9)
    Requirement already satisfied: ipython in /usr/local/lib/python3.7/dist-packages (from openmdao[notebooks]) (5.5.0)
    Requirement already satisfied: pygments in /usr/local/lib/python3.7/dist-packages (from ipython->openmdao[notebooks]) (2.6.1)
    Requirement already satisfied: prompt-toolkit<2.0.0,>=1.0.4 in /usr/local/lib/python3.7/dist-packages (from ipython->openmdao[notebooks]) (1.0.18)
    Requirement already satisfied: simplegeneric>0.8 in /usr/local/lib/python3.7/dist-packages (from ipython->openmdao[notebooks]) (0.8.1)
    Requirement already satisfied: pexpect in /usr/local/lib/python3.7/dist-packages (from ipython->openmdao[notebooks]) (4.8.0)
    Requirement already satisfied: traitlets>=4.2 in /usr/local/lib/python3.7/dist-packages (from ipython->openmdao[notebooks]) (5.1.1)
    Requirement already satisfied: pickleshare in /usr/local/lib/python3.7/dist-packages (from ipython->openmdao[notebooks]) (0.7.5)
    Requirement already satisfied: setuptools>=18.5 in /usr/local/lib/python3.7/dist-packages (from ipython->openmdao[notebooks]) (57.4.0)
    Requirement already satisfied: decorator in /usr/local/lib/python3.7/dist-packages (from ipython->openmdao[notebooks]) (4.4.2)
    Requirement already satisfied: wcwidth in /usr/local/lib/python3.7/dist-packages (from prompt-toolkit<2.0.0,>=1.0.4->ipython->openmdao[notebooks]) (0.2.5)
    Requirement already satisfied: six>=1.9.0 in /usr/local/lib/python3.7/dist-packages (from prompt-toolkit<2.0.0,>=1.0.4->ipython->openmdao[notebooks]) (1.15.0)
    Requirement already satisfied: ipython-genutils in /usr/local/lib/python3.7/dist-packages (from notebook->openmdao[notebooks]) (0.2.0)
    Requirement already satisfied: ipykernel in /usr/local/lib/python3.7/dist-packages (from notebook->openmdao[notebooks]) (4.10.1)
    Requirement already satisfied: nbformat in /usr/local/lib/python3.7/dist-packages (from notebook->openmdao[notebooks]) (5.1.3)
    Requirement already satisfied: terminado>=0.8.1 in /usr/local/lib/python3.7/dist-packages (from notebook->openmdao[notebooks]) (0.12.1)
    Requirement already satisfied: jinja2 in /usr/local/lib/python3.7/dist-packages (from notebook->openmdao[notebooks]) (2.11.3)
    Requirement already satisfied: jupyter-client>=5.2.0 in /usr/local/lib/python3.7/dist-packages (from notebook->openmdao[notebooks]) (5.3.5)
    Requirement already satisfied: nbconvert in /usr/local/lib/python3.7/dist-packages (from notebook->openmdao[notebooks]) (5.6.1)
    Requirement already satisfied: tornado>=4 in /usr/local/lib/python3.7/dist-packages (from notebook->openmdao[notebooks]) (5.1.1)
    Requirement already satisfied: jupyter-core>=4.4.0 in /usr/local/lib/python3.7/dist-packages (from notebook->openmdao[notebooks]) (4.9.1)
    Requirement already satisfied: Send2Trash in /usr/local/lib/python3.7/dist-packages (from notebook->openmdao[notebooks]) (1.8.0)
    Requirement already satisfied: pyzmq>=13 in /usr/local/lib/python3.7/dist-packages (from jupyter-client>=5.2.0->notebook->openmdao[notebooks]) (22.3.0)
    Requirement already satisfied: python-dateutil>=2.1 in /usr/local/lib/python3.7/dist-packages (from jupyter-client>=5.2.0->notebook->openmdao[notebooks]) (2.8.2)
    Requirement already satisfied: ptyprocess in /usr/local/lib/python3.7/dist-packages (from terminado>=0.8.1->notebook->openmdao[notebooks]) (0.7.0)
    Requirement already satisfied: MarkupSafe>=0.23 in /usr/local/lib/python3.7/dist-packages (from jinja2->notebook->openmdao[notebooks]) (2.0.1)
    Requirement already satisfied: entrypoints>=0.2.2 in /usr/local/lib/python3.7/dist-packages (from nbconvert->notebook->openmdao[notebooks]) (0.3)
    Requirement already satisfied: mistune<2,>=0.8.1 in /usr/local/lib/python3.7/dist-packages (from nbconvert->notebook->openmdao[notebooks]) (0.8.4)
    Requirement already satisfied: testpath in /usr/local/lib/python3.7/dist-packages (from nbconvert->notebook->openmdao[notebooks]) (0.5.0)
    Requirement already satisfied: bleach in /usr/local/lib/python3.7/dist-packages (from nbconvert->notebook->openmdao[notebooks]) (4.1.0)
    Requirement already satisfied: pandocfilters>=1.4.1 in /usr/local/lib/python3.7/dist-packages (from nbconvert->notebook->openmdao[notebooks]) (1.5.0)
    Requirement already satisfied: defusedxml in /usr/local/lib/python3.7/dist-packages (from nbconvert->notebook->openmdao[notebooks]) (0.7.1)
    Requirement already satisfied: jsonschema!=2.5.0,>=2.4 in /usr/local/lib/python3.7/dist-packages (from nbformat->notebook->openmdao[notebooks]) (2.6.0)
    Requirement already satisfied: packaging in /usr/local/lib/python3.7/dist-packages (from bleach->nbconvert->notebook->openmdao[notebooks]) (21.3)
    Requirement already satisfied: webencodings in /usr/local/lib/python3.7/dist-packages (from bleach->nbconvert->notebook->openmdao[notebooks]) (0.5.1)
    Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from requests->openmdao[notebooks]) (1.24.3)
    Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests->openmdao[notebooks]) (2.10)
    Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests->openmdao[notebooks]) (2021.10.8)
    Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests->openmdao[notebooks]) (3.0.4)
    Building wheels for collected packages: openmdao, pyDOE2
      Building wheel for openmdao (setup.py) ... done
      Created wheel for openmdao: filename=openmdao-3.15.0-py3-none-any.whl size=5636871 sha256=20a186af89501dd745864ac6a0fe13e9ce736916e7116beee83f33f78e8e3abb
      Stored in directory: /root/.cache/pip/wheels/8e/17/b9/3abc5290645ecebb5df1d069d7e182a1f73f56f179709d33a0
      Building wheel for pyDOE2 (setup.py) ... done
      Created wheel for pyDOE2: filename=pyDOE2-1.3.0-py3-none-any.whl size=25539 sha256=87c3008213b994311e0a0858ac4095d09f7fa5d3241ceef4f1010e8778f1f767
      Stored in directory: /root/.cache/pip/wheels/49/91/2d/d08e80806bf7756193541f6c03c0492af288fcd6158d3d0998
    Successfully built openmdao pyDOE2
    Installing collected packages: pyDOE2, openmdao
    Successfully installed openmdao-3.15.0 pyDOE2-1.3.0
    Collecting dymos[docs]
      Downloading dymos-1.3.0.tar.gz (2.9 MB)
         |████████████████████████████████| 2.9 MB 36.2 MB/s
    Requirement already satisfied: openmdao>=3.13.0 in /usr/local/lib/python3.7/dist-packages (from dymos[docs]) (3.15.0)
    Requirement already satisfied: numpy>=1.14.1 in /usr/local/lib/python3.7/dist-packages (from dymos[docs]) (1.19.5)
    Requirement already satisfied: scipy>=1.0.0 in /usr/local/lib/python3.7/dist-packages (from dymos[docs]) (1.4.1)
    Requirement already satisfied: jupyter in /usr/local/lib/python3.7/dist-packages (from dymos[docs]) (1.0.0)
    Collecting jupyter-book
      Downloading jupyter_book-0.12.1-py3-none-any.whl (43 kB)
         |████████████████████████████████| 43 kB 1.8 MB/s
    Requirement already satisfied: nbconvert in /usr/local/lib/python3.7/dist-packages (from dymos[docs]) (5.6.1)
    Requirement already satisfied: notebook in /usr/local/lib/python3.7/dist-packages (from dymos[docs]) (5.3.1)
    Requirement already satisfied: ipython in /usr/local/lib/python3.7/dist-packages (from dymos[docs]) (5.5.0)
    Collecting numpydoc>=0.9.1
      Downloading numpydoc-1.1.0-py3-none-any.whl (47 kB)
         |████████████████████████████████| 47 kB 5.1 MB/s
    Collecting redbaron
      Downloading redbaron-0.9.2-py2.py3-none-any.whl (34 kB)
    Requirement already satisfied: tabulate in /usr/local/lib/python3.7/dist-packages (from dymos[docs]) (0.8.9)
    Requirement already satisfied: sphinx>=1.6.5 in /usr/local/lib/python3.7/dist-packages (from numpydoc>=0.9.1->dymos[docs]) (1.8.6)
    Requirement already satisfied: Jinja2>=2.3 in /usr/local/lib/python3.7/dist-packages (from numpydoc>=0.9.1->dymos[docs]) (2.11.3)
    Requirement already satisfied: MarkupSafe>=0.23 in /usr/local/lib/python3.7/dist-packages (from Jinja2>=2.3->numpydoc>=0.9.1->dymos[docs]) (2.0.1)
    Requirement already satisfied: pyparsing in /usr/local/lib/python3.7/dist-packages (from openmdao>=3.13.0->dymos[docs]) (3.0.6)
    Requirement already satisfied: networkx>=2.0 in /usr/local/lib/python3.7/dist-packages (from openmdao>=3.13.0->dymos[docs]) (2.6.3)
    Requirement already satisfied: requests in /usr/local/lib/python3.7/dist-packages (from openmdao>=3.13.0->dymos[docs]) (2.23.0)
    Requirement already satisfied: pyDOE2 in /usr/local/lib/python3.7/dist-packages (from openmdao>=3.13.0->dymos[docs]) (1.3.0)
    Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.7/dist-packages (from sphinx>=1.6.5->numpydoc>=0.9.1->dymos[docs]) (1.15.0)
    Requirement already satisfied: Pygments>=2.0 in /usr/local/lib/python3.7/dist-packages (from sphinx>=1.6.5->numpydoc>=0.9.1->dymos[docs]) (2.6.1)
    Requirement already satisfied: snowballstemmer>=1.1 in /usr/local/lib/python3.7/dist-packages (from sphinx>=1.6.5->numpydoc>=0.9.1->dymos[docs]) (2.2.0)
    Requirement already satisfied: imagesize in /usr/local/lib/python3.7/dist-packages (from sphinx>=1.6.5->numpydoc>=0.9.1->dymos[docs]) (1.3.0)
    Requirement already satisfied: alabaster<0.8,>=0.7 in /usr/local/lib/python3.7/dist-packages (from sphinx>=1.6.5->numpydoc>=0.9.1->dymos[docs]) (0.7.12)
    Requirement already satisfied: babel!=2.0,>=1.3 in /usr/local/lib/python3.7/dist-packages (from sphinx>=1.6.5->numpydoc>=0.9.1->dymos[docs]) (2.9.1)
    Requirement already satisfied: sphinxcontrib-websupport in /usr/local/lib/python3.7/dist-packages (from sphinx>=1.6.5->numpydoc>=0.9.1->dymos[docs]) (1.2.4)
    Requirement already satisfied: docutils<0.18,>=0.11 in /usr/local/lib/python3.7/dist-packages (from sphinx>=1.6.5->numpydoc>=0.9.1->dymos[docs]) (0.17.1)
    Requirement already satisfied: packaging in /usr/local/lib/python3.7/dist-packages (from sphinx>=1.6.5->numpydoc>=0.9.1->dymos[docs]) (21.3)
    Requirement already satisfied: setuptools in /usr/local/lib/python3.7/dist-packages (from sphinx>=1.6.5->numpydoc>=0.9.1->dymos[docs]) (57.4.0)
    Requirement already satisfied: pytz>=2015.7 in /usr/local/lib/python3.7/dist-packages (from babel!=2.0,>=1.3->sphinx>=1.6.5->numpydoc>=0.9.1->dymos[docs]) (2018.9)
    Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests->openmdao>=3.13.0->dymos[docs]) (2.10)
    Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests->openmdao>=3.13.0->dymos[docs]) (3.0.4)
    Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests->openmdao>=3.13.0->dymos[docs]) (2021.10.8)
    Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from requests->openmdao>=3.13.0->dymos[docs]) (1.24.3)
    Requirement already satisfied: prompt-toolkit<2.0.0,>=1.0.4 in /usr/local/lib/python3.7/dist-packages (from ipython->dymos[docs]) (1.0.18)
    Requirement already satisfied: decorator in /usr/local/lib/python3.7/dist-packages (from ipython->dymos[docs]) (4.4.2)
    Requirement already satisfied: traitlets>=4.2 in /usr/local/lib/python3.7/dist-packages (from ipython->dymos[docs]) (5.1.1)
    Requirement already satisfied: pickleshare in /usr/local/lib/python3.7/dist-packages (from ipython->dymos[docs]) (0.7.5)
    Requirement already satisfied: pexpect in /usr/local/lib/python3.7/dist-packages (from ipython->dymos[docs]) (4.8.0)
    Requirement already satisfied: simplegeneric>0.8 in /usr/local/lib/python3.7/dist-packages (from ipython->dymos[docs]) (0.8.1)
    Requirement already satisfied: wcwidth in /usr/local/lib/python3.7/dist-packages (from prompt-toolkit<2.0.0,>=1.0.4->ipython->dymos[docs]) (0.2.5)
    Requirement already satisfied: qtconsole in /usr/local/lib/python3.7/dist-packages (from jupyter->dymos[docs]) (5.2.1)
    Requirement already satisfied: ipywidgets in /usr/local/lib/python3.7/dist-packages (from jupyter->dymos[docs]) (7.6.5)
    Requirement already satisfied: jupyter-console in /usr/local/lib/python3.7/dist-packages (from jupyter->dymos[docs]) (5.2.0)
    Requirement already satisfied: ipykernel in /usr/local/lib/python3.7/dist-packages (from jupyter->dymos[docs]) (4.10.1)
    Requirement already satisfied: tornado>=4.0 in /usr/local/lib/python3.7/dist-packages (from ipykernel->jupyter->dymos[docs]) (5.1.1)
    Requirement already satisfied: jupyter-client in /usr/local/lib/python3.7/dist-packages (from ipykernel->jupyter->dymos[docs]) (5.3.5)
    Requirement already satisfied: widgetsnbextension~=3.5.0 in /usr/local/lib/python3.7/dist-packages (from ipywidgets->jupyter->dymos[docs]) (3.5.2)
    Requirement already satisfied: nbformat>=4.2.0 in /usr/local/lib/python3.7/dist-packages (from ipywidgets->jupyter->dymos[docs]) (5.1.3)
    Requirement already satisfied: ipython-genutils~=0.2.0 in /usr/local/lib/python3.7/dist-packages (from ipywidgets->jupyter->dymos[docs]) (0.2.0)
    Requirement already satisfied: jupyterlab-widgets>=1.0.0 in /usr/local/lib/python3.7/dist-packages (from ipywidgets->jupyter->dymos[docs]) (1.0.2)
    Requirement already satisfied: jsonschema!=2.5.0,>=2.4 in /usr/local/lib/python3.7/dist-packages (from nbformat>=4.2.0->ipywidgets->jupyter->dymos[docs]) (2.6.0)
    Requirement already satisfied: jupyter-core in /usr/local/lib/python3.7/dist-packages (from nbformat>=4.2.0->ipywidgets->jupyter->dymos[docs]) (4.9.1)
    Requirement already satisfied: Send2Trash in /usr/local/lib/python3.7/dist-packages (from notebook->dymos[docs]) (1.8.0)
    Requirement already satisfied: terminado>=0.8.1 in /usr/local/lib/python3.7/dist-packages (from notebook->dymos[docs]) (0.12.1)
    Requirement already satisfied: python-dateutil>=2.1 in /usr/local/lib/python3.7/dist-packages (from jupyter-client->ipykernel->jupyter->dymos[docs]) (2.8.2)
    Requirement already satisfied: pyzmq>=13 in /usr/local/lib/python3.7/dist-packages (from jupyter-client->ipykernel->jupyter->dymos[docs]) (22.3.0)
    Requirement already satisfied: ptyprocess in /usr/local/lib/python3.7/dist-packages (from terminado>=0.8.1->notebook->dymos[docs]) (0.7.0)
    Collecting sphinx-external-toc~=0.2.3
      Downloading sphinx_external_toc-0.2.3-py3-none-any.whl (24 kB)
    Collecting sphinx-thebe~=0.0.10
      Downloading sphinx_thebe-0.0.10-py3-none-any.whl (7.7 kB)
    Collecting sphinx-copybutton
      Downloading sphinx_copybutton-0.4.0-py3-none-any.whl (12 kB)
    Collecting sphinx-multitoc-numbering~=0.1.3
      Downloading sphinx_multitoc_numbering-0.1.3-py3-none-any.whl (4.6 kB)
    Collecting sphinx>=1.6.5
      Downloading Sphinx-4.3.1-py3-none-any.whl (3.1 MB)
         |████████████████████████████████| 3.1 MB 36.6 MB/s
    Requirement already satisfied: click~=7.1 in /usr/local/lib/python3.7/dist-packages (from jupyter-book->dymos[docs]) (7.1.2)
    Requirement already satisfied: pyyaml in /usr/local/lib/python3.7/dist-packages (from jupyter-book->dymos[docs]) (3.13)
    Collecting sphinxcontrib-bibtex~=2.2.0
      Downloading sphinxcontrib_bibtex-2.2.1-py3-none-any.whl (31 kB)
    Collecting sphinx-panels~=0.6.0
      Downloading sphinx_panels-0.6.0-py3-none-any.whl (87 kB)
         |████████████████████████████████| 87 kB 7.5 MB/s
    Collecting linkify-it-py~=1.0.1
      Downloading linkify_it_py-1.0.2-py3-none-any.whl (19 kB)
    Collecting sphinx-comments
      Downloading sphinx_comments-0.0.3-py3-none-any.whl (4.6 kB)
    Collecting jupytext<1.12,>=1.11.2
      Downloading jupytext-1.11.5-py3-none-any.whl (292 kB)
         |████████████████████████████████| 292 kB 35.1 MB/s
    Collecting sphinx-jupyterbook-latex~=0.4.6
      Downloading sphinx_jupyterbook_latex-0.4.6-py3-none-any.whl (13 kB)
    Collecting myst-nb~=0.13.1
      Downloading myst_nb-0.13.1-py3-none-any.whl (37 kB)
    Collecting sphinx-book-theme~=0.1.4
      Downloading sphinx_book_theme-0.1.7-py3-none-any.whl (91 kB)
         |████████████████████████████████| 91 kB 11.0 MB/s
    Collecting sphinx-togglebutton
      Downloading sphinx_togglebutton-0.2.3-py3-none-any.whl (6.1 kB)
    Collecting mdit-py-plugins
      Downloading mdit_py_plugins-0.3.0-py3-none-any.whl (43 kB)
         |████████████████████████████████| 43 kB 2.1 MB/s
    Collecting markdown-it-py~=1.0
      Downloading markdown_it_py-1.1.0-py3-none-any.whl (83 kB)
         |████████████████████████████████| 83 kB 2.3 MB/s
    Requirement already satisfied: toml in /usr/local/lib/python3.7/dist-packages (from jupytext<1.12,>=1.11.2->jupyter-book->dymos[docs]) (0.10.2)
    Collecting uc-micro-py
      Downloading uc_micro_py-1.0.1-py3-none-any.whl (6.2 kB)
    Requirement already satisfied: typing-extensions>=3.7.4 in /usr/local/lib/python3.7/dist-packages (from markdown-it-py~=1.0->jupytext<1.12,>=1.11.2->jupyter-book->dymos[docs]) (3.10.0.2)
    Requirement already satisfied: attrs<22,>=19 in /usr/local/lib/python3.7/dist-packages (from markdown-it-py~=1.0->jupytext<1.12,>=1.11.2->jupyter-book->dymos[docs]) (21.2.0)
    Collecting jupyter-cache~=0.4.1
      Downloading jupyter_cache-0.4.3-py3-none-any.whl (31 kB)
    Collecting jupyter-sphinx~=0.3.2
      Downloading jupyter_sphinx-0.3.2-py3-none-any.whl (20 kB)
    Collecting myst-parser~=0.15.2
      Downloading myst_parser-0.15.2-py3-none-any.whl (46 kB)
         |████████████████████████████████| 46 kB 3.7 MB/s
    Requirement already satisfied: importlib-metadata in /usr/local/lib/python3.7/dist-packages (from myst-nb~=0.13.1->jupyter-book->dymos[docs]) (4.8.2)
    Requirement already satisfied: nbclient<0.6,>=0.2 in /usr/local/lib/python3.7/dist-packages (from jupyter-cache~=0.4.1->myst-nb~=0.13.1->jupyter-book->dymos[docs]) (0.5.9)
    Collecting nbdime
      Downloading nbdime-3.1.1-py2.py3-none-any.whl (5.3 MB)
         |████████████████████████████████| 5.3 MB 32.9 MB/s
    Requirement already satisfied: sqlalchemy<1.5,>=1.3.12 in /usr/local/lib/python3.7/dist-packages (from jupyter-cache~=0.4.1->myst-nb~=0.13.1->jupyter-book->dymos[docs]) (1.4.27)
    Collecting mdit-py-plugins
      Downloading mdit_py_plugins-0.2.8-py3-none-any.whl (41 kB)
         |████████████████████████████████| 41 kB 43 kB/s
    Requirement already satisfied: nest-asyncio in /usr/local/lib/python3.7/dist-packages (from nbclient<0.6,>=0.2->jupyter-cache~=0.4.1->myst-nb~=0.13.1->jupyter-book->dymos[docs]) (1.5.1)
    Collecting jupyter-client
      Downloading jupyter_client-7.1.0-py3-none-any.whl (129 kB)
         |████████████████████████████████| 129 kB 72.7 MB/s
    Requirement already satisfied: entrypoints in /usr/local/lib/python3.7/dist-packages (from jupyter-client->ipykernel->jupyter->dymos[docs]) (0.3)
    Requirement already satisfied: defusedxml in /usr/local/lib/python3.7/dist-packages (from nbconvert->dymos[docs]) (0.7.1)
    Requirement already satisfied: mistune<2,>=0.8.1 in /usr/local/lib/python3.7/dist-packages (from nbconvert->dymos[docs]) (0.8.4)
    Requirement already satisfied: bleach in /usr/local/lib/python3.7/dist-packages (from nbconvert->dymos[docs]) (4.1.0)
    Requirement already satisfied: pandocfilters>=1.4.1 in /usr/local/lib/python3.7/dist-packages (from nbconvert->dymos[docs]) (1.5.0)
    Requirement already satisfied: testpath in /usr/local/lib/python3.7/dist-packages (from nbconvert->dymos[docs]) (0.5.0)
    Collecting sphinxcontrib-htmlhelp>=2.0.0
      Downloading sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl (100 kB)
         |████████████████████████████████| 100 kB 10.5 MB/s
    Collecting sphinxcontrib-devhelp
      Downloading sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl (84 kB)
         |████████████████████████████████| 84 kB 3.8 MB/s
    Collecting sphinxcontrib-jsmath
      Downloading sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl (5.1 kB)
    Collecting sphinxcontrib-qthelp
      Downloading sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl (90 kB)
         |████████████████████████████████| 90 kB 10.2 MB/s
    Collecting sphinxcontrib-applehelp
      Downloading sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl (121 kB)
         |████████████████████████████████| 121 kB 75.8 MB/s
    Requirement already satisfied: sphinxcontrib-serializinghtml>=1.1.5 in /usr/local/lib/python3.7/dist-packages (from sphinx>=1.6.5->numpydoc>=0.9.1->dymos[docs]) (1.1.5)
    Requirement already satisfied: beautifulsoup4<5,>=4.6.1 in /usr/local/lib/python3.7/dist-packages (from sphinx-book-theme~=0.1.4->jupyter-book->dymos[docs]) (4.6.3)
    Collecting docutils<0.18,>=0.11
      Downloading docutils-0.16-py2.py3-none-any.whl (548 kB)
         |████████████████████████████████| 548 kB 59.5 MB/s
    Collecting pydata-sphinx-theme~=0.7.2
      Downloading pydata_sphinx_theme-0.7.2-py3-none-any.whl (1.4 MB)
         |████████████████████████████████| 1.4 MB 32.5 MB/s
    Requirement already satisfied: importlib-resources in /usr/local/lib/python3.7/dist-packages (from sphinx-jupyterbook-latex~=0.4.6->jupyter-book->dymos[docs]) (5.4.0)
    Requirement already satisfied: wheel in /usr/local/lib/python3.7/dist-packages (from sphinx-togglebutton->jupyter-book->dymos[docs]) (0.37.0)
    Collecting pybtex>=0.20
      Downloading pybtex-0.24.0-py2.py3-none-any.whl (561 kB)
         |████████████████████████████████| 561 kB 74.6 MB/s
    Collecting pybtex-docutils>=1.0.0
      Downloading pybtex_docutils-1.0.1-py3-none-any.whl (4.8 kB)
    Collecting latexcodec>=1.0.4
      Downloading latexcodec-2.0.1-py2.py3-none-any.whl (18 kB)
    Requirement already satisfied: greenlet!=0.4.17 in /usr/local/lib/python3.7/dist-packages (from sqlalchemy<1.5,>=1.3.12->jupyter-cache~=0.4.1->myst-nb~=0.13.1->jupyter-book->dymos[docs]) (1.1.2)
    Requirement already satisfied: webencodings in /usr/local/lib/python3.7/dist-packages (from bleach->nbconvert->dymos[docs]) (0.5.1)
    Requirement already satisfied: zipp>=0.5 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata->myst-nb~=0.13.1->jupyter-book->dymos[docs]) (3.6.0)
    Collecting GitPython!=2.1.4,!=2.1.5,!=2.1.6
      Downloading GitPython-3.1.24-py3-none-any.whl (180 kB)
         |████████████████████████████████| 180 kB 53.6 MB/s
    Collecting jupyter-server-mathjax>=0.2.2
      Downloading jupyter_server_mathjax-0.2.3-py3-none-any.whl (3.1 MB)
         |████████████████████████████████| 3.1 MB 39.3 MB/s
    Collecting colorama
      Downloading colorama-0.4.4-py2.py3-none-any.whl (16 kB)
    Collecting jupyter-server
      Downloading jupyter_server-1.12.1-py3-none-any.whl (395 kB)
         |████████████████████████████████| 395 kB 75.6 MB/s
    Collecting gitdb<5,>=4.0.1
      Downloading gitdb-4.0.9-py3-none-any.whl (63 kB)
         |████████████████████████████████| 63 kB 1.9 MB/s
    Collecting smmap<6,>=3.0.1
      Downloading smmap-5.0.0-py3-none-any.whl (24 kB)
    Collecting websocket-client
      Downloading websocket_client-1.2.1-py2.py3-none-any.whl (52 kB)
         |████████████████████████████████| 52 kB 1.6 MB/s
    Collecting anyio<4,>=3.1.0
      Downloading anyio-3.4.0-py3-none-any.whl (78 kB)
         |████████████████████████████████| 78 kB 7.5 MB/s
    Requirement already satisfied: prometheus-client in /usr/local/lib/python3.7/dist-packages (from jupyter-server->nbdime->jupyter-cache~=0.4.1->myst-nb~=0.13.1->jupyter-book->dymos[docs]) (0.12.0)
    Requirement already satisfied: argon2-cffi in /usr/local/lib/python3.7/dist-packages (from jupyter-server->nbdime->jupyter-cache~=0.4.1->myst-nb~=0.13.1->jupyter-book->dymos[docs]) (21.1.0)
    Collecting tornado>=4.0
      Downloading tornado-6.1-cp37-cp37m-manylinux2010_x86_64.whl (428 kB)
         |████████████████████████████████| 428 kB 60.1 MB/s
    Collecting sniffio>=1.1
      Downloading sniffio-1.2.0-py3-none-any.whl (10 kB)
    Requirement already satisfied: cffi>=1.0.0 in /usr/local/lib/python3.7/dist-packages (from argon2-cffi->jupyter-server->nbdime->jupyter-cache~=0.4.1->myst-nb~=0.13.1->jupyter-book->dymos[docs]) (1.15.0)
    Requirement already satisfied: pycparser in /usr/local/lib/python3.7/dist-packages (from cffi>=1.0.0->argon2-cffi->jupyter-server->nbdime->jupyter-cache~=0.4.1->myst-nb~=0.13.1->jupyter-book->dymos[docs]) (2.21)
    Requirement already satisfied: qtpy in /usr/local/lib/python3.7/dist-packages (from qtconsole->jupyter->dymos[docs]) (1.11.2)
    Collecting baron>=0.7
      Downloading baron-0.9-py2.py3-none-any.whl (52 kB)
         |████████████████████████████████| 52 kB 884 kB/s
    Collecting rply
      Downloading rply-0.7.8-py2.py3-none-any.whl (16 kB)
    Requirement already satisfied: appdirs in /usr/local/lib/python3.7/dist-packages (from rply->baron>=0.7->redbaron->dymos[docs]) (1.4.4)
    Building wheels for collected packages: dymos
      Building wheel for dymos (setup.py) ... done
      Created wheel for dymos: filename=dymos-1.3.0-py3-none-any.whl size=616306 sha256=70f732b81525c3dd235bc0147d75a1c6bec3f6e69269484e1141ab85654f7ceb
      Stored in directory: /root/.cache/pip/wheels/55/4d/f6/f69f4c08a21baaaa394063b2970afc8d1f2a8488997cdb0a04
    Successfully built dymos
    Installing collected packages: tornado, sniffio, jupyter-client, websocket-client, smmap, anyio, jupyter-server, gitdb, sphinxcontrib-qthelp, sphinxcontrib-jsmath, sphinxcontrib-htmlhelp, sphinxcontrib-devhelp, sphinxcontrib-applehelp, markdown-it-py, latexcodec, jupyter-server-mathjax, GitPython, docutils, colorama, sphinx, pybtex, nbdime, mdit-py-plugins, uc-micro-py, sphinx-togglebutton, rply, pydata-sphinx-theme, pybtex-docutils, myst-parser, jupyter-sphinx, jupyter-cache, sphinxcontrib-bibtex, sphinx-thebe, sphinx-panels, sphinx-multitoc-numbering, sphinx-jupyterbook-latex, sphinx-external-toc, sphinx-copybutton, sphinx-comments, sphinx-book-theme, myst-nb, linkify-it-py, jupytext, baron, redbaron, numpydoc, jupyter-book, dymos
      Attempting uninstall: tornado
        Found existing installation: tornado 5.1.1
        Uninstalling tornado-5.1.1:
          Successfully uninstalled tornado-5.1.1
      Attempting uninstall: jupyter-client
        Found existing installation: jupyter-client 5.3.5
        Uninstalling jupyter-client-5.3.5:
          Successfully uninstalled jupyter-client-5.3.5
      Attempting uninstall: docutils
        Found existing installation: docutils 0.17.1
        Uninstalling docutils-0.17.1:
          Successfully uninstalled docutils-0.17.1
      Attempting uninstall: sphinx
        Found existing installation: Sphinx 1.8.6
        Uninstalling Sphinx-1.8.6:
          Successfully uninstalled Sphinx-1.8.6
    ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
    google-colab 1.0.0 requires tornado~=5.1.0; python_version >= "3.0", but you have tornado 6.1 which is incompatible.
    datascience 0.10.6 requires folium==0.2.1, but you have folium 0.8.3 which is incompatible.
    Successfully installed GitPython-3.1.24 anyio-3.4.0 baron-0.9 colorama-0.4.4 docutils-0.16 dymos-1.3.0 gitdb-4.0.9 jupyter-book-0.12.1 jupyter-cache-0.4.3 jupyter-client-7.1.0 jupyter-server-1.12.1 jupyter-server-mathjax-0.2.3 jupyter-sphinx-0.3.2 jupytext-1.11.5 latexcodec-2.0.1 linkify-it-py-1.0.2 markdown-it-py-1.1.0 mdit-py-plugins-0.2.8 myst-nb-0.13.1 myst-parser-0.15.2 nbdime-3.1.1 numpydoc-1.1.0 pybtex-0.24.0 pybtex-docutils-1.0.1 pydata-sphinx-theme-0.7.2 redbaron-0.9.2 rply-0.7.8 smmap-5.0.0 sniffio-1.2.0 sphinx-4.3.1 sphinx-book-theme-0.1.7 sphinx-comments-0.0.3 sphinx-copybutton-0.4.0 sphinx-external-toc-0.2.3 sphinx-jupyterbook-latex-0.4.6 sphinx-multitoc-numbering-0.1.3 sphinx-panels-0.6.0 sphinx-thebe-0.0.10 sphinx-togglebutton-0.2.3 sphinxcontrib-applehelp-1.0.2 sphinxcontrib-bibtex-2.2.1 sphinxcontrib-devhelp-1.0.2 sphinxcontrib-htmlhelp-2.0.0 sphinxcontrib-jsmath-1.0.1 sphinxcontrib-qthelp-1.0.3 tornado-6.1 uc-micro-py-1.0.1 websocket-client-1.2.1

# Commercial Aircraft Range Maximization by Differential Inclusion

In this example we seek to maximize the range of a commercial aircraft
flying at a constant Mach number with a fixed initial fuel load. The
initial and final altitudes are constrained to 10000 ft (that is, in
this simplified example we're ignoring the aircraft behavior near the
ground).

This example is simplified in several ways. The Mach number is fixed at
0.8. When only considering fuel burn, the aircraft will tend to fly
slower to conserve fuel, ignoring the running costs of being airborne.

For a more detailed optimization of commercial aircraft performance, see
Betts {cite}`betts1995application`.

## Differential Inclusion

In this case we also use the steady flight assumption to allow the
problem to be parameterized with a different control set. Rather than
providing throttle/thrust and angle of attack as control variables, we
use a _differential inclusion_ approach. Whereas altitude and velocity
are often treated as state variables to be integrated, here we take a
slightly different approach.

Rather than using the differential equations to compute rates for the
flight path angle and true airspeed, we use a nonlinear solver to
determine the alpha and thrust time-history which is needed to make the
given altitude and airspeed time-history possible. This technique is
known as differential inclusion. It was demonstrated by Seywald {cite}`Seywald1994`.
Since the collocation
techniques in Dymos are based on polynomial approximations, rates of the
control variables can be easily calculated, which can then be applied to
the reordered dynamics equations to solve for angle of attack and
thrust.

The use of collocation/psuedospectral techniques in solving problems via
differential inclusion has been examined by Fahroo and Ross {cite}`fahroo2001second`
and Kumar and Seywald {cite}`Kumar1996`. Since the differential inclusion
approach demonstrated here relies on nonlinear solvers running within
the ODE model, obtaining accurate derivatives is paramount. Computing
derivatives with finite differences in the presence of nonlinear solvers
can lead to inaccurate derivatives based on the properties of the
solver. With OpenMDAO's unified derivatives framework, the sensitivy to
solver tolerances is greatly reduced.

### Pitfalls of Differential Inclusion

Differential inclusion works well in this case but it is highly
dependent on the parameterization of the problem. For instance, it is
possible for the optimizer to suggest a flight profile that requires
thrust well beyond the capabilities of the vehicle. In our case, the
throttle parameter $\tau$ is path constrained within the optimizer, so
that the optimizer can make the solution physically attainable by our
model.

We have two options for how to tackle this problem. We could specify
altitude as a control variable, and use it's approximate derivative
($alt\_rate$) to provide the altitude rate for the flight
equilibrium conditions. In practice, however, this noisy derivative of
altitude can cause numerical difficulties. Other times, the optimizer
will shape the control to satisfy our equilibrium conditions at the
collocation nodes but be wildly wrong in between these nodes. One simple
way to get around this is to still integrate altitude, but to provide
$climb\_rate$ as the control variable. This makes it easy to
constraint $climb\_rate$ with bounds constraints, and the
time-history of altitude will be smoother since it is the time integral
of the rate of climb.

#### Solver Options

In general, _openmdao.api.AnalysisError_ should be raised
when the design variables provided by the optimizer result in
nonconvergence of the solvers. This informs pyoptsparse to backtrack
along its line search for a solution. This error can be raised by the
_compute_ method of a component. The nonlinear solver can
also raise this error if it fails to converge by setting the following
options:

```python
self.nonlinear_solver.options['maxiter'] = 150
self.nonlinear_solver.options['err_on_maxiter'] = True
```

In the course of solving, an outer NewtonSolver won't necessarily force
subsystems to be solved, instead relying on the derivative at the at the
previous solution. In the context of OpenMDAO, even explicit components
constitute subsystems. This can lead to poorly converged solutions.

Setting the _solve\_subystems_ option to True will force
subsystems to be updated, making sure that all derivative information is
up-to-date.

```python
self.nonlinear_solver.options['solve_subsystems'] = True
self.nonlinear_solver.options['max_sub_solves'] = 10
```

#### The Impact of Choice of Transcription

The high-order Gauss-Lobatto method used by Dymos evaluates the ordinary
differential equations in a two-step process. First, the ODEs are
evaluated at the *state discretization* nodes. Using the state values
and derivatives, values and rates at the *collocation* nodes are then
interpolated. If a large rate is found at one of the state
discretization nodes, the interpolation at the collocation node can be
outside of the expected range, leading to nonconvergence of solvers
within the ODEs.

There is also an overhead cost to invoking solvers. While differential
inclusion reduces the number of design variables for the optimizer, it
also slows down each evaluation of the ODE somewhat. Since evaluating
the collocation constraints in the Gauss-Lobatto method requires two
evaluations of the ODE, it typically suffers greater slowdown that the
Radau Pseudospectral method, which evaluates the defects of the ODE with
a single evaluation that encompasses all the nodes.

For these reasons the Radau Pseudospectral transcription is typically
preferable when using a differential inclusion approach to problems.

## Problem Formulation

### State Variables

|Name        |Description                   |Fixed Initial Value  |Fixed Final Value|
|------------|------------------------------|---------------------|-----------------|
|range       |distance flown along ground   |True (0 NM)          |False            |
|mass\_fuel  |mass of fuel aboard aircraft  |True (30000 lbm)     |True (0 lbm)     |
|alt         |aircraft altitude             |True (10000 ft)      |True (10000 ft)  |

### Dynamic Controls

|Name         |Description             |Optimized  |Fixed Initial Value  |Fixed Final Value|
|-------------|------------------------|-----------|---------------------|-----------------|
|climb\_rate  |aircraft rate of climb  |True       |False                |False            |

### Parameters

|Name           |Description                 |Optimized             |
|---------------|----------------------------|----------------------|
|mach           |mach number                 |False (0.8)           |
|S              |aerodynamic reference area  |False (427.8 m\*\*2)  |
|mass\_empty    |aircraft empty mass         |False (330693.393 lbm)|
|mass\_payload  |aircraft payload mass       |False (74100 lbm)     |

### Objective

|Name   | Description | Location | Minimized or Maximized |
|-------|-------------|----------|------------------------|
|r      | range       | final    | Maximized              |

### Nonlinear Path Constraints

|Name  | Description                 |Lower  |Upper|
|------|-----------------------------|-------|-----|
|tau   | engine throttle parameter   |0.01   |1.0  |

### Nonlinear Boundary Constraints

None

## Models

### Atmosphere

This problem uses an analytic fit to the 1976 standard atmosphere.

|Name  |Description           |Input or Output|
|------|----------------------|---------------|
|alt   |altitude (m)          |input          |
|pres  |static pressure (Pa)  |output         |
|temp  |temperature (K)       |output         |
|sos   |speed of sound (m/s)  |output         |
|rho   |density (kg/m\*\*3)   |output         |

### True Airspeed

_TrueAirspeedComp_ uses the Mach number, provided as a
control, and the speed of sound from the atmosphere model to compute the
true airspeed of the aircraft.

\begin{align}
  TAS &= mach \cdot sos
\end{align}

|Name  |Description           |Input or Output|
|------|----------------------|---------------|
|mach  |Mach number           |input          |
|sos   |speed of sound (m/s)  |input          |
|TAS   |true airspeed (m/s)   |output         |

### Flight Path Angle

_SteadyFlightPathAngleComp_ uses the true airspeed and the
climb rate, obtained by differentiating the altitude time history at the
nodes, to compute the flight path angle.

\begin{align}
  \gamma &= \arctan \frac{TAS}{\dot h}
\end{align}

|Name       |Description              |Input or Output|
|-----------|-------------------------|---------------|
|TAS        |true airspeed (m/s)      |input          |
|alt\_rate  |climb rate (m/s)         |input          |
|gamma      |flight path angle (rad)  |output         |

### Range Rate

_RangeRateComp_ uses the true airspeed and the flight path
angle to determine the velocity projected along the ground. This is the
derivative of the state variable _range_.

\begin{align}
  \dot{range} &= TAS \cos \gamma
\end{align}

|Name        |Description              |Input or Output|
|------------|-------------------------|---------------|
|TAS         |true airspeed (m/s)      |input          |
|gamma       |flight path angle (rad)  |input          |
|dXdt:range  |range rate (m/s)         |output         |

### Mass

The component _MassComp_ defined in
`mass_comp.py` computes the aircraft total mass based on
its empty mass, payload mass, and current fuel mass. It also computes
total weight which simplifies some equations later on.

\begin{align}
  mass_{total} &= mass_{empty} + mass_{payload} + mass_{fuel} W_{total} = 9.80665 \, mass_{total}
\end{align}

|Name           |Description                |Input or Output|
|---------------|---------------------------|---------------|
|mass\_empty    |aircraft empty mass (kg)   |input          |
|mass\_payload  |payload mass (kg)          |input          |
|mass\_fuel     |fuel mass (kg)             |input          |
|mass\_total    |total aircraft mass (kg)   |output         |
|W\_total       |total aircraft weight (N)  |output         |

### Dynamic Pressure

The _DynamicPressureComp_ computes the dynamic pressure from
true airspeed and atmospheric density.

\begin{align}
  q &= \frac{1}{2} \rho TAS^2
\end{align}

|Name  |Description                      |Input or Output|
|------|---------------------------------|---------------|
|TAS   |true airspeed (m/s)              |input          |
|rho   |atmospheric density (kg/m\*\*3)  |input          |
|q     |dynamic pressure (Pa)            |output         |

### Aerodynamics

The aerodynamics group computes the aerodynamic coefficients and forces
on the vehicle. It consists of an interpolation component which outputs
lift, drag, and moment coefficients as a function of Mach number, angle
of attack, altitude, and tail rotation angle. A second component then
uses these coefficients, along with dynamic pressure and aerodynamic
reference area, to compute the lift and drag forces on the vehicle.

The aerodynamics group resides within the flight equilibrium group. As
that group iterates to find the combination of thrust coefficient, angle
of attack, and tail rotation angle, aerodynamics needs to update the
values of the interpolated coefficients and resulting forces.

Organizationally speaking, we logically could have put the dynamic
pressure component within the aerodynamics group. However, since that
group doesn't need to be updated with changes in alpha and tail angle,
it's more efficient to leave it outside of flight equilibrium group.

  |Name   |Description                |Input or Output|
  |-------|---------------------------|---------------|
  |mach   |mach number                |input          |
  |alt    |altitude (m)               |input          |
  |alpha  |angle of attack (deg)      |input          |
  |eta    |tail rotation angle (deg)  |input          |
  |CL     |lift coefficient           |output         |
  |CD     |drag coefficient           |output         |
  |CM     |moment coefficient         |output         |
  |L      |lift force (N)             |output         |
  |D      |drag force (N)             |output         |

```{Note}
This example uses _openmdao.api.MetaModelStructuredComp_ to
interpolate aerodynamic properties of the vehicle. This component is
somewhat easier to use since it is distributed as part of OpenMDAO, but
it can be significantly slower than alternatives such as
[MBI](https://github.com/OpenMDAO/MBI) and
[SMT](https://github.com/SMTorg/smt). The Aerodynamics group includes an
implementation of the aerodynamics coefficients interpolant which uses
MBI that is commented out. By switching to MBI for this component, this
problem will solve roughly 20 times faster.
```

### Flight Equilibrium

The steady flight equilibrium group uses balances to solve for the angle
of attack and tail plane rotation angle such that the aircraft is in
steady flight (the rates of change in flight path angle and true
airspeed are zero) and the aerodynamic moment in the pitch axis is zero.

Of course, in reality the vehicle will accelerate, but the flight
profile being modeled is so benign that assuming steady flight at
discrete points (nodes) in the trajectory is not terribly inaccurate.

The thrust necessary for steady flight is computed by balancing the drag
equation

\begin{align}
  C_T &= W_{total} \frac{sin \gamma}{q \cdot S \cdot \cos \alpha} + \frac{C_D}{\cos \alpha}
\end{align}

The lift coefficient required for steady flight is found by balancing
lift and weight:

\begin{align}
  \tilde{C_L} &= W_{total} \frac{cos \gamma}{q \cdot S} - C_T \sin \alpha
\end{align}

Using coefficients in the balance equations is better scaled from a numerical standpoint.

### Propulsion

Having determined the thrust, the propulsion group then computes the
rate of fuel burn. In addition, by normalizing thrust at any point by
the maximum possible thrust, we obtain the throttle parameter $\tau$.
The propulsion group uses a number of components to perform these
calculations.

\begin{align}
  T &= C_T \cdot q \cdot S
\end{align}

Maximum thrust is computed by multiplying sea-level thrust by the ratio
of pressure to sea-level atmospheric pressure.

\begin{align}
  T_{max} &= T_{max,sl} \frac{P}{P_{sl}}
\end{align}

The throttle parameter is then the ratio current thrust to maximum
possible thrust.

\begin{align}
  \tau &= \frac{T}{T_{max}}
\end{align}

The thrust specific fuel consumption is computed as follows:

\begin{align}
  TSFC &= TSFC_{sl} - 1.5 E - 10 \cdot 9.80665 \cdot alt
\end{align}

Finally, fuel burn rate is:

\begin{align}
  \dot{mass_{fuel}} &= -TSFC \frac{T}{9.80665}
\end{align}

## The ODE System: aircraft_ode.py

```python
class AircraftODE(om.Group):

    def initialize(self):
        self.options.declare('num_nodes', types=int,
                             desc='Number of nodes to be evaluated in the RHS')

    def setup(self):
        nn = self.options['num_nodes']

        self.add_subsystem(name='mass_comp',
                           subsys=MassComp(num_nodes=nn),
                           promotes_inputs=['mass_fuel'])

        self.connect('mass_comp.W_total', 'flight_equilibrium.W_total')

        self.add_subsystem(name='atmos',
                           subsys=USatm1976Comp(num_nodes=nn),
                           promotes_inputs=[('h', 'alt')])

        self.connect('atmos.pres', 'propulsion.pres')
        self.connect('atmos.sos', 'tas_comp.sos')
        self.connect('atmos.rho', 'q_comp.rho')

        self.add_subsystem(name='tas_comp',
                           subsys=TrueAirspeedComp(num_nodes=nn))

        self.connect('tas_comp.TAS',
                     ('gam_comp.TAS', 'q_comp.TAS', 'range_rate_comp.TAS'))

        self.add_subsystem(name='gam_comp',
                           subsys=SteadyFlightPathAngleComp(num_nodes=nn))

        self.connect('gam_comp.gam', ('flight_equilibrium.gam', 'range_rate_comp.gam'))

        self.add_subsystem(name='q_comp',
                           subsys=DynamicPressureComp(num_nodes=nn))

        self.connect('q_comp.q', ('aero.q', 'flight_equilibrium.q', 'propulsion.q'))

        self.add_subsystem(name='flight_equilibrium',
                           subsys=SteadyFlightEquilibriumGroup(num_nodes=nn),
                           promotes_inputs=['aero.*', 'alt'],
                           promotes_outputs=['aero.*'])

        self.connect('flight_equilibrium.CT', 'propulsion.CT')

        self.add_subsystem(name='propulsion', subsys=PropulsionGroup(num_nodes=nn),
                           promotes_inputs=['alt'])

        self.add_subsystem(name='range_rate_comp', subsys=RangeRateComp(num_nodes=nn))

        # We promoted multiple inputs to the group named 'alt'.
        # In order to automatically create a source variable for 'alt', we must specify their units
        # since they have different units in different components.
        self.set_input_defaults('alt', val=np.ones(nn,), units='m')
```

In this case the system has only two integrated states:
_range_ and _mass\_fuel_. There are six parameters.
Two of them (_alt_ and _climb\_rate_) will be varied dynamically in the phase
The other four (_mach_, _S_, _mass\_empty_, and _mass\_payload_), will be set to fixed values as non-optimized parameters.
More details on the various models involved can be found in the examples code.

## Building and running the problem

In the following code we define and solve the optimal control problem.
Note that we demonstrate the use of externally-connected design parameters in this case.
The four parameters are connected to a source provided by the _assumptions_ IndepVarComp.

```python
om.display_source("dymos.examples.aircraft_steady_flight.aircraft_ode")
```

```python
import matplotlib.pyplot as plt

import openmdao.api as om
import dymos as dm

from dymos.examples.aircraft_steady_flight.aircraft_ode import AircraftODE
from dymos.examples.plotting import plot_results
from dymos.utils.lgl import lgl

p = om.Problem(model=om.Group())
p.driver = om.pyOptSparseDriver()
p.driver.options['optimizer'] = 'SLSQP'
p.driver.declare_coloring()

num_seg = 15
seg_ends, _ = lgl(num_seg + 1)

traj = p.model.add_subsystem('traj', dm.Trajectory())

phase = traj.add_phase('phase0',
                       dm.Phase(ode_class=AircraftODE,
                                transcription=dm.Radau(num_segments=num_seg,
                                                       segment_ends=seg_ends,
                                                       order=3, compressed=False)))

# Pass Reference Area from an external source
assumptions = p.model.add_subsystem('assumptions', om.IndepVarComp())
assumptions.add_output('S', val=427.8, units='m**2')
assumptions.add_output('mass_empty', val=1.0, units='kg')
assumptions.add_output('mass_payload', val=1.0, units='kg')

phase.set_time_options(fix_initial=True,
                       duration_bounds=(300, 10000),
                       duration_ref=5600)

phase.add_state('range', units='NM',
                rate_source='range_rate_comp.dXdt:range',
                fix_initial=True, fix_final=False, ref=1e-3,
                defect_ref=1e-3, lower=0, upper=2000)

phase.add_state('mass_fuel', units='lbm',
                rate_source='propulsion.dXdt:mass_fuel',
                fix_initial=True, fix_final=True,
                upper=1.5E5, lower=0.0, ref=1e2, defect_ref=1e2)

phase.add_state('alt', units='kft',
                rate_source='climb_rate',
                fix_initial=True, fix_final=True,
                lower=0.0, upper=60, ref=1e-3, defect_ref=1e-3)

phase.add_control('climb_rate', units='ft/min', opt=True, lower=-3000, upper=3000,
                  targets=['gam_comp.climb_rate'],
                  rate_continuity=True, rate2_continuity=False)

phase.add_control('mach', targets=['tas_comp.mach', 'aero.mach'], units=None, opt=False)

phase.add_parameter('S',
                    targets=['aero.S', 'flight_equilibrium.S', 'propulsion.S'],
                    units='m**2')

phase.add_parameter('mass_empty', targets=['mass_comp.mass_empty'], units='kg')
phase.add_parameter('mass_payload', targets=['mass_comp.mass_payload'], units='kg')

phase.add_path_constraint('propulsion.tau', lower=0.01, upper=2.0)

p.model.connect('assumptions.S', 'traj.phase0.parameters:S')
p.model.connect('assumptions.mass_empty', 'traj.phase0.parameters:mass_empty')
p.model.connect('assumptions.mass_payload', 'traj.phase0.parameters:mass_payload')

phase.add_objective('range', loc='final', ref=-1.0e-4)

phase.add_timeseries_output('aero.CL')
phase.add_timeseries_output('aero.CD')

p.setup()

p['traj.phase0.t_initial'] = 0.0
p['traj.phase0.t_duration'] = 3600.0
p['traj.phase0.states:range'] = phase.interp('range', ys=(0, 724.0))
p['traj.phase0.states:mass_fuel'] = phase.interp('mass_fuel', ys=(30000, 1e-3))
p['traj.phase0.states:alt'][:] = 10.0

p['traj.phase0.controls:mach'][:] = 0.8

p['assumptions.S'] = 427.8
p['assumptions.mass_empty'] = 0.15E6
p['assumptions.mass_payload'] = 84.02869 * 400

dm.run_problem(p)

exp_out = traj.simulate()

plot_results([('traj.phase0.timeseries.states:range', 'traj.phase0.timeseries.states:alt',
               'range (NM)', 'altitude (kft)'),
              ('traj.phase0.timeseries.time', 'traj.phase0.timeseries.states:mass_fuel',
               'time (s)', 'fuel mass (lbm)'),
              ('traj.phase0.timeseries.time', 'traj.phase0.timeseries.CL',
               'time (s)', 'lift coefficient'),
              ('traj.phase0.timeseries.time', 'traj.phase0.timeseries.CD',
               'time (s)', 'drag coefficient')],
             title='Commercial Aircraft Optimization',
             p_sol=p, p_sim=exp_out)

plt.show()
```

```python
from openmdao.utils.assert_utils import assert_near_equal

assert_near_equal(p.get_val('traj.phase0.timeseries.states:range', units='NM')[-1],
                  726.85, tolerance=1.0E-2)

assert_near_equal(p.get_val('traj.phase0.timeseries.CL')[0],
                  exp_out.get_val('traj.phase0.timeseries.CL')[0],
                  tolerance=1.0E-9)

assert_near_equal(p.get_val('traj.phase0.timeseries.CD')[0],
                  exp_out.get_val('traj.phase0.timeseries.CD')[0],
                  tolerance=1.0E-9)
```

## References

```{bibliography}
:filter: docname in docnames
```


            prepared by Özlem Salehi (QTurkey)

This cell contains some macros. If there is a problem with displaying mathematical formulas, please run this cell to load these macros.

$\newcommand{\Mod}[1]{\ (\mathrm{mod}\ #1)}$
$ \newcommand{\bra}[1]{\langle #1|} $
$ \newcommand{\ket}[1]{|#1\rangle} $
$ \newcommand{\braket}[2]{\langle #1|#2\rangle} $
$ \newcommand{\dot}[2]{ #1 \cdot #2} $
$ \newcommand{\biginner}[2]{\left\langle #1,#2\right\rangle} $
$ \newcommand{\mymatrix}[2]{\left( \begin{array}{#1} #2\end{array} \right)} $
$ \newcommand{\myvector}[1]{\mymatrix{c}{#1}} $
$ \newcommand{\myrvector}[1]{\mymatrix{r}{#1}} $
$ \newcommand{\mypar}[1]{\left( #1 \right)} $
$ \newcommand{\mybigpar}[1]{ \Big( #1 \Big)} $
$ \newcommand{\sqrttwo}{\frac{1}{\sqrt{2}}} $
$ \newcommand{\dsqrttwo}{\dfrac{1}{\sqrt{2}}} $
$ \newcommand{\onehalf}{\frac{1}{2}} $
$ \newcommand{\donehalf}{\dfrac{1}{2}} $
$ \newcommand{\hadamard}{ \mymatrix{rr}{ \sqrttwo & \sqrttwo \\ \sqrttwo & -\sqrttwo }} $
$ \newcommand{\vzero}{\myvector{1\\0}} $
$ \newcommand{\vone}{\myvector{0\\1}} $
$ \newcommand{\stateplus}{\myvector{ \sqrttwo \\  \sqrttwo } } $
$ \newcommand{\stateminus}{ \myrvector{ \sqrttwo \\ -\sqrttwo } } $
$ \newcommand{\myarray}[2]{ \begin{array}{#1}#2\end{array}} $
$ \newcommand{\X}{ \mymatrix{cc}{0 & 1 \\ 1 & 0}  } $
$ \newcommand{\Z}{ \mymatrix{rr}{1 & 0 \\ 0 & -1}  } $
$ \newcommand{\Htwo}{ \mymatrix{rrrr}{ \frac{1}{2} & \frac{1}{2} & \frac{1}{2} & \frac{1}{2} \\ \frac{1}{2} & -\frac{1}{2} & \frac{1}{2} & -\frac{1}{2} \\ \frac{1}{2} & \frac{1}{2} & -\frac{1}{2} & -\frac{1}{2} \\ \frac{1}{2} & -\frac{1}{2} & -\frac{1}{2} & \frac{1}{2} } } $
$ \newcommand{\CNOT}{ \mymatrix{cccc}{1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0} } $
$ \newcommand{\norm}[1]{ \left\lVert #1 \right\rVert } $
$ \newcommand{\pstate}[1]{ \lceil \mspace{-1mu} #1 \mspace{-1.5mu} \rfloor } $

# Shor's Algorithm in More Detail

In this notebook we will analyze Shor's algorithm step by step to gain more intuition. Let's start by examining the phase estimation and order finding stages in more detail, in the context of factoring problem.

More about phase estimation circuit

Recall the controlled operations in the phase estimation circuit. We will examine the effect of applying a series of controlled $ U $ operators. Let $ U $ be an operator with eigenvector $ \ket{\psi} $ and the corresponding eigenvalue $ e^{2\pi i \phi } $.

We start with the initial state $ \ket{0}^{\otimes t}\ket{\psi}$.

After applying Hadamard to first register we get $ \displaystyle \frac{1}{2^{t/2}} \sum_{k=0}^{2^t-1} \ket{k}\ket{\psi}  $.

Let $ k=k_1 ... k_t $ be the state of the first register. Let's apply the first controlled gate, $ CU^{2^0} $ where qubit $t$ is the control.

If $ k_t=0 $, new state is  $\ket{k} \ket{\psi} $. If $ k_t=1 $, new state is $ e^{2\pi i \phi 2^0} \ket{k} \ket{\psi} $.

Hence, we can write it as, $\ket{k} U^{k_t 2 ^0} \ket{\psi}.$

Task 1 (on paper)

Show that after applying the controlled $U^{2^j}$ gates for $j=0,\dots,t-1$, the state obtained can be expressed as $\displaystyle \frac{1}{2^{t/2}} \sum_{k=0}^{2^t-1} \ket{k}U^k\ket{\psi}$.

click for our solution

As you can see, we have transformed the quantum state $\displaystyle \frac{1}{2^{t/2}} \sum_{k=0}^{2^t-1} \ket{k}\ket{\psi}$ into the state $\displaystyle \frac{1}{2^{t/2}} \sum_{k=0}^{2^t-1} \ket{k}U^k\ket{\psi}$ after applying the controlled $U$ gates.

Order finding revisited

Now let's see what happens in the case of order finding algorithm. Recall the definition of $ U $.

$
U \ket{y} \rightarrow \ket{xy \Mod{N}}
$

Combining with what we have deduced above, in Shor's algorithm after the controlled operations our quantum states look like

$ \displaystyle
\frac{1}{{2^{t/2}}}\sum_{k=0}^{{2^t}-1}\ket{k}\ket{1} \rightarrow 	 \frac{1}{2^{t/2}} \sum_{k=0}^{2^t-1} \ket{k}\ket{x^k \Mod{N}}.
$

Note that since $x^r \Mod{N}=1$, the second register will be in a superposition of integers which are periodic. In the rest of the discussion, we will omit the multiplers.

$\ket{x^0 \Mod{N}}+\ket{x^1 \Mod{N}}+ \ket{x^2 \Mod{N}}+\cdots +\ket{x^r \Mod{N}}+\ket{x^{r+1} \Mod{N}}+\ket{x^{r+2} \Mod{N}}+ \cdots+ \ket{x^{2^t -1} \Mod{N}}$

$$=\ket{1}+\ket{x}+\ket{x^2 \Mod{N}}+\cdots +\ket{1}+\ket{x}+\ket{x^2 \Mod{N}} + \cdots$$

Let's express the state of both registers.

$$\ket{0}\ket{1}+\ket{1}\ket{x}+\ket{2}\ket{x^2 \Mod{N}}+\cdots +\ket{r}\ket{1}+\ket{r+1}\ket{x}+\ket{r+2}\ket{x^2 \Mod{N}} + \cdots$$

At this stage let's measure the second register and say that $\ket{j}$ is observed. Then the first register will be holding integers $\ket{k}$ such that $x^k \Mod{N} = j$. Hence, the first register will be in the following superposition:

$$\ket{x_0}+ \ket{x_0+r}+ \ket{x_0+2r}+ \ket{x_0+3r} + \cdots  $$

Recall that in the Discrete Fourier Transform notebook, we had observed that when $DFT$ was applied on a periodic list of size $N$ with period $r$, then the transformed list had peaks around integer multiples of $N/r$.

The same happens here. We apply inverse $QFT$ to the first register and end up with states of the following form in the first register.

$$
\ket{0} + \ket{2^t/r} + \ket{2 \cdot 2^t/r} + \ket{3 \cdot 2^t/r} + \dots
$$

When we measure the first register, we observe $s\cdot 2^t /r$ for some $s$. Hence by dividing with $2^t$, we obtain an estimate for $\frac{s}{r}$, from which we extract $r$ by continued fractions algorithm.

Summary

- Start with the state
$ \ket{0}\ket{1} .$

- Apply Hadamard to first register.

$\displaystyle \frac{1}{\sqrt{2^t}} \sum_{k=0}^{2^t-1} \ket{k}\ket{1}$

- Apply controlled operations.

$
  \displaystyle \frac{1}{\sqrt{2^t}} \sum_{k=0}^{2^t-1} \ket{k}\ket{x^k \Mod{N}}
$

- Measure the second register and continue with the first register.

$
\displaystyle \sqrt{\frac{r}{2^t}} \sum_{x=0}^{2^t/r -1} \ket{x_0+xr}
$

- Apply inverse $QFT$ to the first register.

$
\displaystyle \sqrt{\frac{r}{2^t}} \sum_{k=0}^{2^t-1} \frac{1}{\sqrt{2^{t} } }
\sum_{x=0}^{2^t/r-1}  e^{-\frac{2\pi i(rx+x_0)k}{2^t}} \ket{k}
$

- Measure the first register. Divide the state you observe by $2^t$ and apply continued fractions algorithm.

Let's analyze the last step in more detail. Probability of observing a particular state $ \ket{k} $ is given by $
\displaystyle  \frac{1}{r} \left | \frac{r}{2^t}  \sum_{x=0}^{2^t/r-1}   e^{-\frac{2\pi irxk}{ {2^t}}  }  \right |^2.
$

Let's run a python code to plot these probabilities in particular case of $r=6$ and $t=11$.

```python
import math
from math import pi
from cmath import exp

r=6
t=11
probabilities=[]
s=0
for k in range(2**t):
    for x in range(math.ceil((2**t)/r)):
        s+=exp(-(2*pi*1j*r*x*k)/(2**t))
    s*=r/(2**t)
    s=((abs(s))**2)/r
    probabilities.append(s)
```

```python
import matplotlib.pyplot as plt
plt.plot(probabilities)
plt.ylabel('Probability of oberving state k')
plt.xlabel('k')
plt.show()
```

Probability peaks around the integer multiples of $2^t/r $ so that with probability (approximately) $ \frac{1}{r} $, one of the states $ \ket{s \cdot  2^t/r} $ is observed for $ s=0,\dots,r-1 $.

```python
print("Observed states:")
for s in range (6):
    print(round(s*2**t/r))
```

    Observed states:
    0
    341
    683
    1024
    1365
    1707

$ \frac{s \cdot 2^t/r}{2^t} $ is an estimate for $ \frac{s}{r} $ and we apply continued fractions algorithm to extract $ r $.

# Shor's Algorithm Revisited

Now combining what we have seen so far, we will go over Shor's Algorithm step by step to understand what is happening behind. Let's recall the algorithm.

Algorithm

- Pick $x$ randomly in the range $1$ to $N-1$, such that $gcd(x,N)=1$.
- Use order finding algorithm to find order of $x\Mod{N}$, which will be denoted by $r$.
- If $r$ is even, and $x^{r/2} \neq -1 \Mod{N}$, then compute $gcd(x^{r/2} -1, N)$ and  $gcd(x^{r/2}+1, N)$.
- Test to see if one of these is a non-trivial factor. If so return, otherwise the algorithm fails. If that is the case, repeat.

As we have discussed previously, Shor's algorithm uses order finding algorithm which is a special case of quantum phase estimation where the operator whose phase is estimated is $ U_x \ket{y} \rightarrow \ket{xy {\Mod{N}}}$.

Factoring 15

- We choose a random $x<N$ which is co-prime with $15$. For this example, let us pick $x=7$. However it is important to mention that it can be the case that this randomly chosen $x$ may not work. For the ease of demonstration we have chosen a value which will work.

- We will compute the order of $7 \Mod{15}$, using the order finding algorithm. $ L=4 $, $\varepsilon = 0.1$ and $t=2L+1+\left \lceil \log \left (2 + \frac{1}{2\varepsilon} \right) \right \rceil =12$.

- Let's begin with $\ket{0} \ket{1}$. Instead of binary representation of the states, we will use integer representations.  Applying Hadamard to first register we get:

\begin{align*}
\frac{1}{\sqrt{2^{12}}} \sum_{k=0}^{2^{12}-1} \ket{k}\ket{1} =\frac{1}{\sqrt{2^{12}}} \bigl (\ket{0} \ket{1}+\ket{1}\ket{1}+\ket{2}\ket{1}+...+\ket{2^{12}-1} \ket{1} \bigr )
\end{align*}

- Noting that $y$ is equal to $1$, after applying the controlled operations we get:

\begin{align*}
&\frac{1}{\sqrt{2^{12}}} \sum_{k=0}^{2^{12}-1} \ket{k}\ket{x^k \Mod{N}}\\
=&\frac{1}{\sqrt{2^{12}}}\bigl (\ket{0}\ket{1}+\ket{1}\ket{7}+\ket{2}\ket{4}+\ket{3}\ket{13}+\ket{4}\ket{1}+\ket{5}\ket{7}+\ket{6}\ket{4}+...+\ket{2^{12}-1}\ket{...} \bigr )
\end{align*}

Task 2

Implement the order finding procedure until the Inverse Quantum Fourier Transform and check whether you obtain the above state. Simulate the circuit without measuring it. Use the function dirac_notation() to print the state representation after getting the results. Check the first 5 states, convert to integer representation and compare with the above expression.

Recall that to implement $CU$ operator you should pass $x$ and $N$ as parameter to the function Ux. Run the following cell to load the function.

CU=Ux(x,N)

```python
%run operator.py
```

```python
#Your code here

s = cirq.Simulator()
results=s.simulate(circuit)
print(results.dirac_notation())
```

click for our solution

At this stage we should apply $QFT^{\dagger}$ to the first register and measure it. After this procedure, second register will be in one of the states  $\ket{1},\ket{7},\ket{4},\ket{13}$.

To be able to analyze the effect of $QFT^{\dagger}$ better, let's measure the second register before applying $QFT^{\dagger}$ to the first register. Let's suppose that the second register is in state $\ket{4}$ after the measurement.

The first register looks like the following:

\begin{align}
\sqrt{\frac{4}{2^{12}}}\bigl(\ket{2}+\ket{6}+\ket{10}+\ket{14}+\cdots + \ket{4094}\bigr).
\end{align}

Note that in our case, $ r $ is a power of 2 and we have exactly $\frac{2^{12}}{2^2}  $ states in the above sum. We can also express the above state as follows. This is also the quantum state to which $QFT^{\dagger}$ is applied.

$$
\frac{1}{\sqrt{2^{10}}} \sum_{x=0}^{2^{10}-1} \ket{4x +2}
$$

Task 3

Measure the second register and sample the circuit. Next, simulate the circuit and print the obtained state using dirac_notation(). Check the first five states and convert to integer representation.

```python
#Your code here

```

click for our solution

Now let's try to understand the effects of ${QFT}^{\dagger}$ on the first register. But first let's remember the definition.

$$
{QFT}^{\dagger} \ket{j} = \frac{1}{\sqrt{2^t}} \sum_{k=0}^{2^{t}-1} e^{-\frac{2\pi ijk}{2^t}} \ket{k}\\
$$

After applying ${QFT}^{\dagger}$, amplitude of a specific state $ \ket{k} $ will be the summation of the amplitudes obtained after applying $\displaystyle {QFT}^{\dagger}$ to each state in the superposition $\frac{1}{\sqrt{2^{10}}} \sum_{x=0}^{2^{10}-1} \ket{4x +2}$. For instance, let's apply ${QFT}^{\dagger}$ to $ \ket{2} $.

\begin{align*}
{QFT}^{\dagger} \ket{2} = &\ket{0} + e^{-\frac{2\pi i\cdot2}{4096}} \ket{1} +e^{-\frac{2\pi i\cdot2\cdot2}{4096}} \ket{2} + \cdots+ e^{-\frac{2\pi i\cdot2\cdot1024}{4096}} \ket{1024}+\\
&\cdots +e^{-\frac{2\pi i\cdot2\cdot2048}{4096}} \ket{2048}+\cdots +e^{-\frac{2\pi i\cdot2\cdot3072}{4096}} \ket{3072}+ \cdots +e^{-\frac{2\pi i\cdot2\cdot4095}{4096}} \ket{4095}
\end{align*}

Note that for states $ \ket{0} $, $ \ket{1024} $, $ \ket{2048} $ and $ \ket{3072} $, the coefficients are equal to 1.

Similarly, we apply $QFT^{\dagger}$ to states $\ket{6}$, $ \ket{10} $, $ \dots $, $ \ket{4094} $. Overall, the state obtained after applying $QFT^{\dagger}$ can be expressed as

$$
\frac{1}{\sqrt{2^{10}}} \sum_{x=0}^{2^{10}-1} \frac{1}{\sqrt{2^{12} } } \sum_{k=0}^{2^{12}-1}  e^{-\frac{2\pi i(4x+2)k}{4096} } \ket{k}.
$$

The probability of observing state $ \ket{k} $ is given by

$$
\left | \frac{1}{\sqrt{2^{10}\cdot2^{12} } } \sum_{x=0}^{2^{10}-1}  e^{-\frac{2\pi i(4x+2)k}{4096} }   \right |^2.
$$

We can drop the coefficient $ e^{\frac{2\pi i(2k)}{4096} }  $ as it is constant for all $ k $ and after getting absolute value and getting squared it is equal to 1. Now let's rewrite the state.

$$
\frac{1}{4} \left | \frac{1}{2^{10}} \sum_{x=0}^{2^{10}-1}  e^{-\frac{2\pi i(4xk)}{4096} }  \right |^2
$$

Note that when $ 4k $ is a multiple of $ 4096 $, then the coefficients are equal to 1 for all $ 2^10 $ values of $ x $. Therefore, for each $ k=0,1024,2048  $ and $ 3072 $,  the probability of observing state $ \ket{k} $ is equal to $ \frac{1}{4} $. Note that in this example $ r $ is a power of $ 2 $ and the probabilities peak exactly around the multiples of $ \frac{2^t}{r} $.

To sum up, after applying $QFT^{\dagger}$ we measure the states $\ket{0},\ket{1024},\ket{2048},\ket{3072}$ each with probability $\frac{1}{4}$.

Task 4

Apply $QFT^{\dagger}$ to the first register and measure (Don't forget to provide a different key for measurement). Sample the circuit and check whether you get the outcomes 0, 1024, 2048 and 3072.

```python
%load iqft.py
```

```python
#Your code here

```

click for our solution

At this stage, we should run the continued fractions algorithm to find out $r$ and compute $gcd(x^{r/2} -1, N)$ and $gcd(x^{r/2}+1, N)$ to find out the factors of 15.


```python
%matplotlib inline
```

PyTorch를 이용한 신경망-변환(Neural-Transfer)
======================================================
**저자**: `Alexis Jacq <https://alexis-jacq.github.io>`_
  **번역**: `김봉모 <http://fmttm.egloos.com>`_

소개
------------------

환영합니다!. 이 문서는 Leon A. Gatys와 Alexander S. Ecker, Matthias Bethge 가 개발한
알고리즘인 `Neural-Style <https://arxiv.org/abs/1508.06576>`__ 를 구현하는 방법에 대해
설명하는 튜토리얼입니다.

신경망 뭐라고?
~~~~~~~~~~~~~~~~~~~

신경망 스타일(Neural-Style), 혹은 신경망 변화(Neural-Transfer)는 콘텐츠 이미지(예, 거북이)와
스타일 이미지(예, 파도를 그린 예술 작품) 을 입력으로 받아 콘텐츠 이미지의 모양대로 스타일 이미지의
'그리는 방식'을 이용해 그린 것처럼 결과를 내는 알고리즘입니다:

.. figure:: /_static/img/neural-style/neuralstyle.png
   :alt: content1

어떻게 동작합니까?
~~~~~~~~~~~~~~~~~~~~~~~

원리는 간단합니다. 2개의 거리(distance)를 정의합니다. 하나는 콘텐츠( $D_C$ )를 위한 것이고
다른 하나는 스타일( $D_S$ )을 위한 것입니다.
$D_C$ 는 콘텐츠 이미지와 스타일 이미지 간의 콘텐츠가 얼마나 차이가 있는지 측정을 합니다.
반면에, $D_S$ 는 콘텐츠 이미지와 스타일 이미지 간의 스타일에서 얼마나 차이가 있는지를 측정합니다.
그런 다음, 세 번째 이미지를 입력(예, 노이즈로 구성된 이미지)으로부터 콘텐츠 이미지와의 콘텐츠 거리
및 스타일 이미지와의 스타일 거리를 최소화하는 방향으로 세 번째 이미지를 변환합니다.

그래서. 어떻게 동작하냐고요?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

자, 더 나아가려면 수학이 필요합니다. $C_{nn}$ 를 사전 훈련된 깊은 합성곱 신경망
네트워크(pre-trained deep convolutional neural network)라고 하고, $X$ 를 어떤 이미지라고 해보겠습니다.
$C_{nn}(X)$ 은 입력 이미지 X를 입력으로 해서 CNN 을 통과한 네트워크(모든 레이어들의 특징 맵(feature map)을 포함하는)를 의미합니다.
$F_{XL} \in C_{nn}(X)$ 는 깊이 레벨 L에서의 특징 맵(feature map)을 의미하고,
모두 벡터화(vectorized)되고 연결된(concatenated) 하나의 단일 벡터입니다.
그리고, $Y$ 를 이미지 $X$ 와 크기가 같은 이미지라고 하면,
레이어 $L$ 에 해당하는 콘텐츠의 거리를 정의할 수 있습니다:

\begin{align}D_C^L(X,Y) = \|F_{XL} - F_{YL}\|^2 = \sum_i (F_{XL}(i) - F_{YL}(i))^2\end{align}

$F_{XL}(i)$ 는 $F_{XL}$ 의 $i^{번째}$ 요소(element) 입니다.
스타일에 해당하는 내용은 위 내용보다 조금 더 신경 쓸 부분이 있습니다.
$F_{XL}^k$ 를 레이어 $L$ 에서 특징 맵(feature map) $K$ 의 $k^{번째}$ 에 해당하는
벡터화된 $k \leq K$ 라고 해 보겠습니다.
스타일 $G_{XL}$ 의 $X$ 레이어에서 $L$ 은 모든 벡터화된 특징 맵(feature map) $F_{XL}^k$
에서 $k \leq K$ 그람(Gram)으로 정의 됩니다.
다시 말하면, $G_{XL}$ 는 $K$\ x\ $K$ 행렬과 요소 $G_{XL}(k,l)$ 의 $k^{번째}$ 줄과
$l^{번째}$ 행의 $G_{XL}$ 는 $F_{XL}^k$ 와 $F_{XL}^l$ 간의
벡터화 곱을 의미합니다:

\begin{align}G_{XL}(k,l) = \langle F_{XL}^k, F_{XL}^l\rangle = \sum_i F_{XL}^k(i) . F_{XL}^l(i)\end{align}

$F_{XL}^k(i)$ 는 $F_{XL}^k$ 의 $i^{번째}$ 요소 입니다.
우리는 $G_{XL}(k,l)$ 를 특징 맵(feature map) $k$ 와 $l$ 간의
상관 관계(correlation)에 대한 척도로 볼 수 있습니다.
그런 의미에서, $G_{XL}$ 는 특징 맵(feature map) $X$ 의 레이어 $L$ 에서의
상관 관계 행렬을 나타냅니다.
$G_{XL}$ 의 크기는 단지 특징 맵(feature map)의 숫자에만 의존성이 있고,
$X$ 의 크기에는 의존성이 없다는 것을 유의 해야 합니다.
그러면, 만약 $Y$ 가 다른 *어떤 크기의* 이미지라면,
우리는 다음과 같이 레이어 $L$ 에서 스타일의 거리를 정의 합니다.

\begin{align}D_S^L(X,Y) = \|G_{XL} - G_{YL}\|^2 = \sum_{k,l} (G_{XL}(k,l) - G_{YL}(k,l))^2\end{align}

$D_C(X,C)$ 의 한 번의 최소화를 위해서, 이미지 변수 $X$ 와 대상 콘텐츠-이미지 $C$ 와
$D_S(X,S)$ 와 $X$ 와 대상 스타일-이미지 $S$ , 둘 다 여러 레이어들에 대해서 계산되야 하고,
우리는 원하는 레이어 각각에서의 거리의 그라디언트를 계산하고 더합니다( $X$ 와 관련된 도함수):

\begin{align}\nabla_{	extit{total}}(X,S,C) = \sum_{L_C} w_{CL_C}.\nabla_{	extit{content}}^{L_C}(X,C) + \sum_{L_S} w_{SL_S}.\nabla_{	extit{style}}^{L_S}(X,S)\end{align}

$L_C$ 와 $L_S$ 는 각각 콘텐츠와 스타일의 원하는 (임의 상태의) 레이어들을 의미하고,
$w_{CL_C}$ 와 $w_{SL_S}$ 는 원하는 레이어에서의
스타일 또는 콘텐츠의 가중치를 (임의 상태의) 의미합니다.
그리고 나서, 우리는 $X$ 에 대해 경사 하강법을 실행합니다.

\begin{align}X \leftarrow X - \alpha \nabla_{	extit{total}}(X,S,C)\end{align}

네, 수학은 이정도면 충분합니다. 만약 더 깊이 알고 싶다면 (그레이언트를 어떻게 계산하는지),
Leon A. Gatys and AL가 작성한 **원래의 논문을 읽어 볼 것을 권장합니다**
논문에는 앞서 설명한 내용들 모두에 대해 보다 자세하고 명확하게 얘기합니다.

구현을 위해서 PyTorch에서는 이미 우리가 필요로하는 모든 것을 갖추고 있습니다.
실제로 PyTorch를 사용하면 라이브러리의 함수를 사용하는 동안 모든 그라디언트(Gradient)가
자동,동적으로 계산됩니다.(라이브러리에서 함수를 사용하는 동안)
이런 점이 PyTorch에서 알고리즘 구현을 매우 편리하게 합니다.

PyTorch 구현
----------------------

위의 모든 수학을 이해할 수 없다면, 구현함으로써 이해도를 높여 갈 수 있을 것 입니다.
PyTorch를 이용할 예정이라면, 먼저 이 문서 :doc:`Introduction to PyTorch </beginner/deep_learning_60min_blitz>` 를 읽어볼 것을 추천 합니다.

패키지들
~~~~~~~~

우리는 다음 패키지들을 활용 할 것입니다:

-  ``torch`` , ``torch.nn``, ``numpy`` (PyTorch로 신경망 처리를 위한 필수 패키지)
-  ``torch.optim`` (효율적인 그라디언트 디센트)
-  ``PIL`` , ``PIL.Image`` , ``matplotlib.pyplot`` (이미지를 읽고 보여주는 패키지)
-  ``torchvision.transforms`` (PIL타입의 이미지들을 토치 텐서 형태로 변형해주는 패키지)
-  ``torchvision.models`` (사전 훈련된 모델들의 학습 또는 읽기 패키지)
-  ``copy`` (모델들의 깊은 복사를 위한 시스템 패키지)

```python
from __future__ import print_function

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

from PIL import Image
import matplotlib.pyplot as plt

import torchvision.transforms as transforms
import torchvision.models as models

import copy
```

쿠다(CUDA)
~~~~~~~~~~~~~~

컴퓨터에 GPU가 있는 경우, 특히 VGG와 같이 깊은 네트워크를 사용하려는 경우
알고리즘을 CUDA 환경에서 실행하는 것이 좋습니다.
CUDA를 쓰기 위해서 Pytorch에서는 ``torch.cuda.is_available()`` 를 제공하는데,
작업하는 컴퓨터에서 GPU 사용이 가능하면 ``True`` 를 리턴 합니다.
이후로, 우리는 ``.cuda()`` 라는 메소드를 사용하여 모듈과 관련된 할당된 프로세스를 CPU에서 GPU로 수 있습니다.
이 모듈을 CPU로 되돌리고 싶을 때에는 (예 : numpy에서 사용), 우리는 ``.cpu ()`` 메소드를 사용하면 됩니다.
마지막으로, ``.type(dtype)`` 메소드는 ``torch.FloatTensor`` 타입을
GPU에서 사용 할 수 있도록 ``torch.cuda.FloatTensor`` 로 변환하는데 사용할 수 있습니다.

```python
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
```

이미지 읽기
~~~~~~~~~~~~~

구현을 간단하게 하기 위해서, 스타일 이미지와 콘텐츠 이미지의 크기를 동일하게 맞추어서 시작합니다.
그런 다음 원하는 출력 이미지 크기로 확장 시킵니다.(본 예제에서는 128이나 512로 하는데 GPU가 가능한 상황에 맞게 선택해서 하세요.)
그리고 영상 데이터를 토치 텐서로 변환하고, 신경망 네트워크에 사용할 수 있도록 준비합니다.

.. Note::
    튜토리얼을 실행하는 데 필요한 이미지를 다운로드하는 링크는 다음과 같습니다.:
    `picasso.jpg <http://pytorch.org/tutorials/_static/img/neural-style/picasso.jpg>`__ 와
    `dancing.jpg <http://pytorch.org/tutorials/_static/img/neural-style/dancing.jpg>`__.
    위 두개의 이미지를 다운로드 받아 디렉토리 이름 ``images`` 에 추가하세요.

```python
# 출력 이미지의 원하는 크기를 정하세요.
imsize = 512 if torch.cuda.is_available() else 128  # gpu가 없다면 작은 크기로

loader = transforms.Compose([
    transforms.Resize(imsize),  # 입력 영상 크기를 맞춤
    transforms.ToTensor()])  # 토치 텐서로 변환

def image_loader(image_name):
    image = Image.open(image_name)
    # 네트워크의 입력 차원을 맞추기 위해 필요한 가짜 배치 차원
    image = loader(image).unsqueeze(0)
    return image.to(device, torch.float)

style_img = image_loader("./data/images/neural-style/picasso.jpg")
content_img = image_loader("./data/images/neural-style/dancing.jpg")

assert style_img.size() == content_img.size(), \
    "we need to import style and content images of the same size"
```

가져온 PIL 이미지는 0에서 255 사이의 이미지 픽셀값을 가집니다.
토치 텐서로 변환하면 0에서 1의 값으로 변환됩니다.
이는 중요한 디테일로: 토치 라이브러리의 신경망은 0에서 1의 텐서 이미지로 학습하게 됩니다.
0-255 텐서 이미지를 네트워크에 공급 하려고 하면 활성화된(activated) 특징 맵(feature map)은 의미가 없습니다.(역자주, 입력 값에 따라 RELU와 같은 활성화 레이어에서 입력으로 되는 값의 범위가 완전히 다르기 때문)
Caffe 라이브러리의 사전 훈련된 네트워크의 경우는 그렇지 않습니다: 해당 모델들은 0에서 255 사이 값의 텐서 이미지로 학습 되었습니다.

이미지 표시하기
~~~~~~~~~~~~~~~~~~~~

우리는 이미지를 표시하기 위해 ``plt.imshow`` 를 이용합니다.
그러기 위해 우선 텐서를 PIL 이미지로 변환해 주겠습니다:

```python
unloader = transforms.ToPILImage()  # PIL 이미지로 재변환 합니다

plt.ion()

def imshow(tensor, title=None):
    image = tensor.cpu().clone()  # 텐서의 값에 변화가 적용되지 않도록 텐서를 복제합니다
    image = image.squeeze(0)      # 페이크 배치 차원을 제거 합니다
    image = unloader(image)
    plt.imshow(image)
    if title is not None:
        plt.title(title)
    plt.pause(0.001) # 그리는 부분이 업데이트 될 수 있게 잠시 정지합니다

plt.figure()
imshow(style_img, title='Style Image')

plt.figure()
imshow(content_img, title='Content Image')
```

콘텐츠 로스
~~~~~~~~~~~~

콘텐츠 로스는 네트워크에서 $X$ 로 입력을 받았을 때 레이어 $L$ 에서 특징 맵(feature map) $F_{XL}$ 을 입력으로 가져 와서
이 이미지와 콘텐츠 이미지 사이의 가중치 콘텐츠 거리 $w_{CL}.D_C^L(X,C)$ 를 반환하는 기능입니다.
따라서, 가중치 $w_{CL}$ 및 목표 콘텐츠 $F_{CL}$ 은 함수의 파라미터 입니다.
우리는 이 매개 변수를 입력으로 사용하는 생성자(constructor)가 있는 토치 모듈로 함수를 구현합니다.
거리 $\|F_{XL} - F_{YL}\|^2$ 는 세 번째 매개 변수로 명시된 기준 ``nn.MSELoss`` 를 사용하여
계산할 수 있는 두 세트의 특징 맵(feature map) 사이의 평균 제곱 오차(MSE, Mean Square Error)입니다.

우리는 신경망의 추가 모듈로서 각 레이어에 컨텐츠 로스를 추가 할 것 입니다.
이렇게 하면 입력 영상 $X$ 를 네트워크에 보낼 때마다 원하는 모든 레이어에서
모든 컨텐츠 로스가 계산되고 자동 그라디언트로 인해 모든 그라디언트가 계산됩니다.
이를 위해 우리는 입력을 리턴하는 ``forward`` 메소드를 만들기만 하면 됩니다: 모듈은 신경망의 ''투명 레이어'' 가 됩니다.
계산된 로스는 모듈의 매개 변수로 저장됩니다.

마지막으로 그라디언트를 재구성하기 위해 nn.MSELoss의 ``backward`` 메서드를 호출하는 가짜 backward 메서드를 정의 합니다.
이 메서드는 계산된 로스를 반환 합니다. 이는 스타일 및 콘텐츠 로스의 진화를 표시하기 위해 그라디언트 디센트를 실행할 때 유용합니다.

```python
class ContentLoss(nn.Module):

    def __init__(self, target,):
        super(ContentLoss, self).__init__()
        # 그라디언트를 동적으로 계산하는 데 사용되는 트리에서 대상 콘텐츠를 '분리' 합니다.
        # :이 값은 변수(variable)가 아니라 명시된 값입니다.
        # 그렇지 않으면 기준의 전달 메소드가 오류를 발생 시킵니다.
        self.target = target.detach()

    def forward(self, input):
        self.loss = F.mse_loss(input, self.target)
        return input
```

.. Note::
   **중요한 디테일**: 이 모듈은 ``ContentLoss`` 라고 이름 지어졌지만 진정한 PyTorch Loss 함수는 아닙니다. 컨텐츠 손실을 PyTorch Loss로 정의 하려면 PyTorch autograd Function을 생성 하고 ``backward`` 메소드에서 직접 그라디언트를 재계산/구현 해야 합니다.

스타일 로스
~~~~~~~~~~~~~~~~~~

스타일 손실을 위해 우리는 레이어 $L$ 에서 $X$ 로 공급된(입력으로 하는) 신경망의 특징 맵(feature map) $F_{XL}$ 이 주어진 경우
그램 생성 $G_{XL}$ 을 계산하는 모듈을 먼저 정의 해야 합니다.
$\hat{F}_{XL}$ 을 KxN 행렬에 대한 $F_{XL}$의 모양을 변경한 버전이라고 하겠습니다.
여기서, $K$는 레이어 $L$에서의 특징 맵(feature map)들의 수이고, $N$ 은 임의의 벡터화 된 특징 맵(feature map) $F_{XL}^k$ 의 길이가 됩니다.
$F_{XL}^k$ 의 $k^{번째}$ 번째 줄은 $F_{XL}^k$ 입니다.
math:`\hat{F}_{XL} \cdot \hat{F}_{XL}^T = G_{XL}` 인지 확인 해보길 바랍니다.
이를 확인해보면 모듈을 구현하는 것이 쉬워 집니다:

```python
def gram_matrix(input):
    a, b, c, d = input.size()  # a=배치 크기(=1)
    # b=특징 맵의 크기
    # (c,d)=특징 맵(N=c*d)의 차원

    features = input.view(a * b, c * d)  # F_XL을 \hat F_XL로 크기 조정합니다

    G = torch.mm(features, features.t())  # 그램 곱을 수행합니다

    # 그램 행렬의 값을 각 특징 맵의 요소 숫자로 나누는 방식으로 '정규화'를 수행합니다.
    return G.div(a * b * c * d)
```

특징 맵(feature map) 차원 $N$이 클수록, 그램(Gram) 행렬의 값이 커집니다.
따라서 $N$으로 정규화하지 않으면 첫번째 레이어에서 계산된 로스 (풀링 레이어 전에)는
경사 하강법 동안 훨씬 더 중요하게 됩니다. (역자주 : 정규화를 하지 않으면 첫번째 레이어에서 계산된 값들의 가중치가 높아져 상대적으로 다른 레이어에서 계산한 값들의 반영이 적게 되버리기 때문에 정규화가 필요해집니다.)
스타일 특징의 흥미로운 부분들은 가장 깊은 레이어에 있기 때문에 그렇게 동작하지 않도록 해야 합니다!

그런 다음 스타일 로스 모듈은 콘텐츠 로스 모듈과 완전히 동일한 방식으로 구현되지만
대상과 입력 간의 그램 매트릭스의 차이를 비교하게 됩니다

```python
class StyleLoss(nn.Module):

    def __init__(self, target_feature):
        super(StyleLoss, self).__init__()
        self.target = gram_matrix(target_feature).detach()

    def forward(self, input):
        G = gram_matrix(input)
        self.loss = F.mse_loss(G, self.target)
        return input
```

뉴럴 네트워크 읽기
~~~~~~~~~~~~~~~~~~~~~~~

자, 우리는 사전 훈련된 신경망을 가져와야 합니다. 이 논문에서와 같이,
우리는 19 레이어 층을 가지는 VGG(VGG19) 네트워크를 사전 훈련된 네트워크로 사용할 것입니다.

PyTorch의 VGG 구현은 두 개의 하위 순차 모듈로 나뉜 모듈 입니다.
``특징(features)`` 모듈 : 합성곱과 풀링 레이어들을 포함 합니다.
``분류(classifier)`` 모듈 : fully connected 레이어들을 포함 합니다.
우리는 여기서 ``특징`` 모듈에 관심이 있습니다.
일부 레이어는 학습 및 평가에 있어서 상황에 따라 다른 동작을 합니다.
이후 우리는 그것을 특징 추출자로 사용하고 있습니다.
우리는 .eval() 을 사용하여 네트워크를 평가 모드로 설정 할 수 있습니다.

```python
cnn = models.vgg19(pretrained=True).features.to(device).eval()
```

또한 VGG 네트워크는 평균 = [0.485, 0.456, 0.406] 및 표준편차 = [0.229, 0.224, 0.225]로 정규화 된 각 채널의 이미지에 대해 학습된 모델입니다.
(역자, 일반적으로 네트워크는 이미지넷으로 학습이 되고 이미지넷 데이터의 평균과 표준편차가 위의 값과 같습니다.)
우리는 입력 이미지를 네트워크로 보내기 전에 정규화 하는데 위 평균과 표준편차 값을 사용합니다.

```python
cnn_normalization_mean = torch.tensor([0.485, 0.456, 0.406]).to(device)
cnn_normalization_std = torch.tensor([0.229, 0.224, 0.225]).to(device)

# 입력 이미지를 정규화하는 모듈을 만들어 nn.Sequential에 쉽게 입력 할 수 있게 하세요.
class Normalization(nn.Module):
    def __init__(self, mean, std):
        super(Normalization, self).__init__()
        # .view(텐서의 모양을 바꾸는 함수)로 평균과 표준 편차 텐서를 [C x 1 x 1] 형태로 만들어
        # 바로 입력 이미지 텐서의 모양인 [B x C x H x W] 에 연산할 수 있도록 만들어 주세요.
        # B는 배치 크기, C는 채널 값, H는 높이, W는 넓이 입니다.

        self.mean = torch.tensor(mean).view(-1, 1, 1)
        self.std = torch.tensor(std).view(-1, 1, 1)

    def forward(self, img):
        # img 값 정규화(normalize)
        return (img - self.mean) / self.std
```

``순차(Sequential)`` 모듈에는 하위 모듈의 정렬된 목록이 있습니다.
예를 들어 ``vgg19.features`` 은 vgg19 구조의 올바른 순서로 정렬된 순서 정보(Conv2d, ReLU, MaxPool2d, Conv2d, ReLU ...)를 포함합니다.
콘텐츠 로스 섹션에서 말했듯이 우리는 네트워크의 원하는 레이어에 추가 레이어 '투명(transparent)'레이어로 스타일 및 콘텐츠 손실 모듈을 추가하려고 합니다.
이를 위해 새로운 순차 모듈을 구성합니다.이 모듈에서는 vgg19의 모듈과 손실 모듈을 올바른 순서로 추가합니다.

```python
# 스타일/콘텐츠 로스로 계산하길 원하는 깊이의 레이어들:
content_layers_default = ['conv_4']
style_layers_default = ['conv_1', 'conv_2', 'conv_3', 'conv_4', 'conv_5']

def get_style_model_and_losses(cnn, normalization_mean, normalization_std,
                               style_img, content_img,
                               content_layers=content_layers_default,
                               style_layers=style_layers_default):
    cnn = copy.deepcopy(cnn)

    # 표준화(normalization) 모듈
    normalization = Normalization(normalization_mean, normalization_std).to(device)

    # 단지 반복 가능한 접근을 갖거나 콘텐츠/스타일의 리스트를 갖기 위함
    # 로스값
    content_losses = []
    style_losses = []

    # cnn은 nn.Sequential 하다고 가정하므로, 새로운 nn.Sequential을 만들어
    # 우리가 순차적으로 활성화 하고자하는 모듈들을 넣겠습니다.
    model = nn.Sequential(normalization)

    i = 0  # conv레이어를 찾을때마다 값을 증가 시킵니다
    for layer in cnn.children():
        if isinstance(layer, nn.Conv2d):
            i += 1
            name = 'conv_{}'.format(i)
        elif isinstance(layer, nn.ReLU):
            name = 'relu_{}'.format(i)
            # in-place(입력 값을 직접 업데이트) 버전은 콘텐츠로스와 스타일로스에
            # 좋은 결과를 보여주지 못합니다.
            # 그래서 여기선 out-of-place로 대체 하겠습니다.
            layer = nn.ReLU(inplace=False)
        elif isinstance(layer, nn.MaxPool2d):
            name = 'pool_{}'.format(i)
        elif isinstance(layer, nn.BatchNorm2d):
            name = 'bn_{}'.format(i)
        else:
            raise RuntimeError('Unrecognized layer: {}'.format(layer.__class__.__name__))

        model.add_module(name, layer)

        if name in content_layers:
            # 콘텐츠 로스 추가:
            target = model(content_img).detach()
            content_loss = ContentLoss(target)
            model.add_module("content_loss_{}".format(i), content_loss)
            content_losses.append(content_loss)

        if name in style_layers:
            # 스타일 로스 추가:
            target_feature = model(style_img).detach()
            style_loss = StyleLoss(target_feature)
            model.add_module("style_loss_{}".format(i), style_loss)
            style_losses.append(style_loss)

    # 이제 우리는 마지막 콘텐츠 및 스타일 로스 이후의 레이어들을 잘라냅니다.
    for i in range(len(model) - 1, -1, -1):
        if isinstance(model[i], ContentLoss) or isinstance(model[i], StyleLoss):
            break

    model = model[:(i + 1)]

    return model, style_losses, content_losses
```

.. Note::
   논문에서는 맥스 풀링(Max Pooling) 레이어를 에버리지 풀링(Average Pooling) 레이어로 바꾸는 것을 추천합니다.
   AlexNet에서는 논문에서 사용된 VGG19 네트워크보다 상대적으로 작은 네트워크라 결과 품질에서
   큰 차이를 확인하기 어려울 수 있습니다.
   그러나, 만약 당신이 대체해 보기를 원한다면 아래 코드들을 사용할 수 있습니다:

   ::

       # avgpool = nn.AvgPool2d(kernel_size=layer.kernel_size,
       #                         stride=layer.stride, padding = layer.padding)
       # model.add_module(name,avgpool)

입력 이미지
~~~~~~~~~~~~~~~~~~~

다시, 코드를 간단하게 하기 위해, 콘텐츠와 스타일 이미지들의 같은 차원의 이미지를 가져옵니다.
해당 이미지는 백색 노이즈일 수 있거나 콘텐츠-이미지의 값들을 복사해도 좋습니다.

```python
input_img = content_img.clone()
# 대신에 백색 노이즈를 이용하길 원한다면 아래 줄의 주석처리를 제거하세요:
# input_img = torch.randn(content_img.data.size(), device=device)

# 원본 입력 이미지를 창에 추가합니다:
plt.figure()
imshow(input_img, title='Input Image')
```

경사 하강법
~~~~~~~~~~~~~~~~

알고리즘의 저자인 Len Gatys 가 `여기서 <https://discuss.pytorch.org/t/pytorch-tutorial-for-neural-transfert-of-artistic-style/336/20?u=alexis-jacq>`__ 제안한 방식대로
경사 하강법을 실행하는데 L-BFGS 알고리즘을 사용 하겠습니다.
일반적인 네트워크 학습과는 다르게, 우리는 콘텐츠/스타일 로스를 최소화 하는 방향으로 입력 영상을 학습 시키려고 합니다.
우리는 간단히 PyTorch L-BFGS 옵티마이저 ``optim.LBFGS`` 를 생성하려고 하며, 최적화를 위해 입력 이미지를 텐서 타입으로 전달합니다.
우리는 ``.requires_grad_()`` 를 사용하여 해당 이미지가 그라디언트가 필요함을 확실하게 합니다.

```python
def get_input_optimizer(input_img):
    # 이 줄은 입력은 그레이던트가 필요한 파라미터라는 것을 보여주기 위해 있습니다.
    optimizer = optim.LBFGS([input_img.requires_grad_()])
    return optimizer
```

**마지막 단계**: 경사 하강의 반복. 각 단계에서 우리는 네트워크의 새로운 로스를 계산하기 위해
업데이트 된 입력을 네트워크에 공급해야 합니다. 우리는 그라디언트를 동적으로 계산하고
그라디언트 디센트의 단계를 수행하기 위해 각 손실의 ``역방향(backward)`` 메소드를 실행해야 합니다.
옵티마이저는 인수로서 "클로저(closure)"를 필요로 합니다: 즉, 모델을 재평가하고 로스를 반환 하는 함수입니다.

그러나, 여기에 작은 함정이 있습니다. 최적화 된 이미지는 0 과 1 사이에 머물지 않고 $-\infty$과 $+\infty$ 사이의 값을 가질 수 있습니다.
다르게 말하면, 이미지는 잘 최적화될 수 있고(0-1 사이의 정해진 값 범위내의 값을 가질 수 있고) 이상한 값을 가질 수도 있습니다.
사실 우리는 입력 이미지가 올바른 범위의 값을 유지할 수 있도록 제약 조건 하에서 최적화를 수행해야 합니다.
각 단계마다 0-1 간격으로 값을 유지하기 위해 이미지를 수정하는 간단한 해결책이 있습니다.

```python
def run_style_transfer(cnn, normalization_mean, normalization_std,
                       content_img, style_img, input_img, num_steps=300,
                       style_weight=1000000, content_weight=1):
    """스타일 변환을 실행합니다."""
    print('Building the style transfer model..')
    model, style_losses, content_losses = get_style_model_and_losses(cnn,
        normalization_mean, normalization_std, style_img, content_img)
    optimizer = get_input_optimizer(input_img)

    print('Optimizing..')
    run = [0]
    while run[0] <= num_steps:

        def closure():
            # 입력 이미지의 업데이트된 값들을 보정합니다
            input_img.data.clamp_(0, 1)

            optimizer.zero_grad()
            model(input_img)
            style_score = 0
            content_score = 0

            for sl in style_losses:
                style_score += sl.loss
            for cl in content_losses:
                content_score += cl.loss

            style_score *= style_weight
            content_score *= content_weight

            loss = style_score + content_score
            loss.backward()

            run[0] += 1
            if run[0] % 50 == 0:
                print("run {}:".format(run))
                print('Style Loss : {:4f} Content Loss: {:4f}'.format(
                    style_score.item(), content_score.item()))
                print()

            return style_score + content_score

        optimizer.step(closure)

    # 마지막 보정...
    input_img.data.clamp_(0, 1)

    return input_img
```

마지막으로, 알고리즘을 실행 시킵니다.

```python
output = run_style_transfer(cnn, cnn_normalization_mean, cnn_normalization_std,
                            content_img, style_img, input_img)

plt.figure()
imshow(output, title='Output Image')

# sphinx_gallery_thumbnail_number = 4
plt.ioff()
plt.show()
```