├── .pre-commit.py ├── .travis.yml ├── LICENSE ├── MANIFEST ├── README.md ├── dist ├── Examples_v1.zip ├── PyTrajectory-0.3.3.tar.gz ├── PyTrajectory-0.3.3.zip ├── PyTrajectory-0.3.4.tar.gz ├── PyTrajectory-0.3.4.zip ├── PyTrajectory-0.4.0.tar.gz ├── PyTrajectory-0.4.0.zip ├── PyTrajectory-1.0.0.tar.gz ├── PyTrajectory-1.0.0.zip ├── PyTrajectory-1.1.0.tar.gz ├── PyTrajectory-1.1.0.zip ├── PyTrajectory-1.2.0.tar.gz └── PyTrajectory-1.2.0.zip ├── doc ├── Makefile ├── pic │ ├── acrobot.gif │ ├── acrobot.png │ ├── aircraft.gif │ ├── aircraft.png │ ├── con_double_integrator.gif │ ├── con_double_pend_swing.gif │ ├── con_inv_pend_swing.gif │ ├── inv_3_bar_pend.gif │ ├── inv_dual_pend_swing.gif │ ├── inv_dual_pendulum.png │ ├── inv_pend_swing.gif │ ├── inv_pend_swing.png │ ├── inv_pend_trans.gif │ ├── inv_pendulum.png │ ├── uact_manipulator.gif │ └── uact_manipulator.png ├── pythonhosted_files │ ├── PyTrajectory_1.2.0_pythonhosted.zip │ ├── _static │ │ ├── basic.css │ │ ├── default.css │ │ ├── doctools.js │ │ ├── jquery.js │ │ ├── pygments.css │ │ ├── sidebar.js │ │ └── underscore.js │ └── index.html └── source │ ├── conf.py │ ├── guide │ ├── about.rst │ ├── approach.rst │ ├── background.rst │ ├── examples │ │ ├── acrobot.rst │ │ ├── aircraft.rst │ │ ├── con_double_integrator.rst │ │ ├── con_double_pendulum.rst │ │ ├── con_inv_pendulum_swing.rst │ │ ├── index.rst │ │ ├── inv_3_bar_pend.rst │ │ ├── inv_dual_pendulum_swing.rst │ │ ├── inv_pendulum_trans.rst │ │ └── uact_manipulator.rst │ ├── index.rst │ ├── notes.rst │ └── start.rst │ ├── index.rst │ ├── pytrajectory.rst │ └── requirements.txt ├── examples ├── ex0_InvertedPendulumSwingUp.py ├── ex1_InvertedPendulumTranslation.py ├── ex2_InvertedDualPendulumSwingUp.py ├── ex3_Aircraft.py ├── ex4_UnderactuatedManipulator.py ├── ex5_Acrobot.py ├── ex6_ConstrainedDoubleIntegrator.py ├── ex7_ConstrainedInvertedPendulum.py ├── ex8_ConstrainedDoublePendulum.py ├── ex9_TriplePendulum.py └── misc │ └── ex_n_bar_pend.py ├── pytrajectory ├── __init__.py ├── auxiliary.py ├── collocation.py ├── log.py ├── simulation.py ├── solver.py ├── splines.py ├── system.py ├── trajectories.py └── visualisation.py ├── setup.py └── test ├── conftest.py ├── test_auxiliary.py └── test_examples.py /.pre-commit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | ''' 4 | Git pre commit hook: write latest commit's datetime 5 | 6 | This script writes the date and time of the pending commit into 7 | the source file `doc/index.rst` for the documentation and 8 | the file `__init__.py` of pytrajectory such that it is obvious 9 | to which current code version they belong. 10 | 11 | If changed, this file has to be copied as 'pre-commit' to 12 | '/path/to/repo/pytrajectory/.git/hooks/' 13 | and should be made executable for the changes to take effect. 14 | ''' 15 | 16 | import os 17 | import sys 18 | import time 19 | 20 | # get current date and time 21 | datetime = time.strftime('%Y-%m-%d %H:%M:%S') 22 | datetime_str = "This documentation is built automatically from the source code (commit: {})\n".format(datetime) 23 | 24 | # specify the paths to the files where to replace the placeholder of `datetime` in 25 | file_paths = [['doc', 'source', 'index.rst'], 26 | ['pytrajectory', '__init__.py']] 27 | 28 | # alter the files 29 | for path in file_paths: 30 | try: 31 | with open(os.sep.join(path), mode='r+') as f: 32 | # read the file's lines 33 | in_lines = f.readlines() 34 | 35 | if f.name.endswith('.rst'): 36 | # get the line in which the datetime string will be written 37 | idx = in_lines.index('.. Placeholder for the datetime string of latest commit\n') + 2 38 | 39 | # replace the placeholder 40 | out_lines = in_lines[:idx] + [datetime_str] + in_lines[idx+1:] 41 | elif f.name.endswith('.py'): 42 | # get the line in which the datetime string will be written 43 | idx = in_lines.index('# Placeholder for the datetime string of latest commit\n') + 1 44 | 45 | # replace the placeholder 46 | out_lines = in_lines[:idx] + ['__date__ = "{}"\n'.format(datetime)] + in_lines[idx+1:] 47 | 48 | # rewind the file 49 | f.seek(0) 50 | 51 | # write the output 52 | f.writelines(out_lines) 53 | except Exception as err: 54 | print "Could not change file: {}".format(path[-1]) 55 | print err.message 56 | print "Commit will be aborted!" 57 | sys.exit(1) 58 | 59 | # add the files to the commit 60 | for path in file_paths: 61 | f_path = os.sep.join(path) 62 | 63 | try: 64 | os.system('git add {}'.format(f_path)) 65 | except Exception as err: 66 | print err.message 67 | print "Commit will be aborted!" 68 | sys.exit(1) 69 | 70 | 71 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - "2.7" 5 | 6 | before_install: 7 | - wget http://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh 8 | - chmod +x miniconda.sh 9 | - ./miniconda.sh -b 10 | - export PATH=/home/travis/miniconda2/bin:$PATH 11 | - export DEPS="sympy=0.7.5 numpy=1.8 scipy=0.13 matplotlib ipython pytest" 12 | - conda update --yes --no-deps conda 13 | - conda update --yes conda 14 | 15 | install: 16 | - conda install --yes -c conda conda-env 17 | - conda create -n testenv --yes $DEPS pip python=$TRAVIS_PYTHON_VERSION 18 | - source activate testenv 19 | - python setup.py install 20 | 21 | # command to run tests 22 | script: py.test -v 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 20013-2016 Oliver Schnabel, Andreas Kunze, Carsten Knoll 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | a. Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | b. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | c. Neither the name of PyTrajectory nor the names of its contributors 14 | may be used to endorse or promote products derived from this software 15 | without specific prior written permission. 16 | 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 28 | DAMAGE. 29 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | setup.py 3 | pytrajectory/__init__.py 4 | pytrajectory/auxiliary.py 5 | pytrajectory/collocation.py 6 | pytrajectory/log.py 7 | pytrajectory/simulation.py 8 | pytrajectory/solver.py 9 | pytrajectory/splines.py 10 | pytrajectory/system.py 11 | pytrajectory/trajectories.py 12 | pytrajectory/visualisation.py 13 | test/test_auxiliary.py 14 | test/test_examples.py 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Constrained Double Pendulum](https://raw.github.com/TUD-RST/pytrajectory/master/doc/pic/con_double_pend_swing.gif) 2 | 3 | About 4 | ===== 5 | 6 | [![Build Status](https://travis-ci.org/TUD-RST/pytrajectory.svg?branch=master)](https://travis-ci.org/TUD-RST/pytrajectory) 7 | [![PyPI version](https://badge.fury.io/py/PyTrajectory.svg)](http://badge.fury.io/py/PyTrajectory) 8 | 9 | PyTrajectory is a Python library for trajectory generation for nonlinear control systems. 10 | It relies on solving a boundary value problem (bvp) via a collocation method. It is based 11 | on the scientific work of Graichen, et al. (see references in 12 | [*Background*](https://pytrajectory.readthedocs.org/en/master/guide/background.html) section 13 | of the documentation), but does not depend on proprietary code like Matlabs *bvp4c*. 14 | 15 | It is developed at Dresden University of Technology at the 16 | [Institute for Control Theory](http://www.et.tu-dresden.de/rst/), see also 17 | [other control related software](http://www.tu-dresden.de/rst/software). 18 | 19 | Documentation 20 | ============= 21 | 22 | The documentation can be found at [readthedocs.org](https://pytrajectory.readthedocs.org). 23 | 24 | Examples 25 | ======== 26 | 27 | See the *Usage* or *Examples* section of the documentation. The examples files can also be downloaded 28 | [here](https://github.com/TUD-RST/pytrajectory/tree/master/dist). 29 | 30 | The animation above belongs to example 8. 31 | -------------------------------------------------------------------------------- /dist/Examples_v1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/dist/Examples_v1.zip -------------------------------------------------------------------------------- /dist/PyTrajectory-0.3.3.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/dist/PyTrajectory-0.3.3.tar.gz -------------------------------------------------------------------------------- /dist/PyTrajectory-0.3.3.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/dist/PyTrajectory-0.3.3.zip -------------------------------------------------------------------------------- /dist/PyTrajectory-0.3.4.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/dist/PyTrajectory-0.3.4.tar.gz -------------------------------------------------------------------------------- /dist/PyTrajectory-0.3.4.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/dist/PyTrajectory-0.3.4.zip -------------------------------------------------------------------------------- /dist/PyTrajectory-0.4.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/dist/PyTrajectory-0.4.0.tar.gz -------------------------------------------------------------------------------- /dist/PyTrajectory-0.4.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/dist/PyTrajectory-0.4.0.zip -------------------------------------------------------------------------------- /dist/PyTrajectory-1.0.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/dist/PyTrajectory-1.0.0.tar.gz -------------------------------------------------------------------------------- /dist/PyTrajectory-1.0.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/dist/PyTrajectory-1.0.0.zip -------------------------------------------------------------------------------- /dist/PyTrajectory-1.1.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/dist/PyTrajectory-1.1.0.tar.gz -------------------------------------------------------------------------------- /dist/PyTrajectory-1.1.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/dist/PyTrajectory-1.1.0.zip -------------------------------------------------------------------------------- /dist/PyTrajectory-1.2.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/dist/PyTrajectory-1.2.0.tar.gz -------------------------------------------------------------------------------- /dist/PyTrajectory-1.2.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/dist/PyTrajectory-1.2.0.zip -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build2 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # User-friendly check for sphinx-build2 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pytrajectory.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pytrajectory.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/pytrajectory" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pytrajectory" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /doc/pic/acrobot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/doc/pic/acrobot.gif -------------------------------------------------------------------------------- /doc/pic/acrobot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/doc/pic/acrobot.png -------------------------------------------------------------------------------- /doc/pic/aircraft.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/doc/pic/aircraft.gif -------------------------------------------------------------------------------- /doc/pic/aircraft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/doc/pic/aircraft.png -------------------------------------------------------------------------------- /doc/pic/con_double_integrator.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/doc/pic/con_double_integrator.gif -------------------------------------------------------------------------------- /doc/pic/con_double_pend_swing.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/doc/pic/con_double_pend_swing.gif -------------------------------------------------------------------------------- /doc/pic/con_inv_pend_swing.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/doc/pic/con_inv_pend_swing.gif -------------------------------------------------------------------------------- /doc/pic/inv_3_bar_pend.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/doc/pic/inv_3_bar_pend.gif -------------------------------------------------------------------------------- /doc/pic/inv_dual_pend_swing.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/doc/pic/inv_dual_pend_swing.gif -------------------------------------------------------------------------------- /doc/pic/inv_dual_pendulum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/doc/pic/inv_dual_pendulum.png -------------------------------------------------------------------------------- /doc/pic/inv_pend_swing.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/doc/pic/inv_pend_swing.gif -------------------------------------------------------------------------------- /doc/pic/inv_pend_swing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/doc/pic/inv_pend_swing.png -------------------------------------------------------------------------------- /doc/pic/inv_pend_trans.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/doc/pic/inv_pend_trans.gif -------------------------------------------------------------------------------- /doc/pic/inv_pendulum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/doc/pic/inv_pendulum.png -------------------------------------------------------------------------------- /doc/pic/uact_manipulator.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/doc/pic/uact_manipulator.gif -------------------------------------------------------------------------------- /doc/pic/uact_manipulator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/doc/pic/uact_manipulator.png -------------------------------------------------------------------------------- /doc/pythonhosted_files/PyTrajectory_1.2.0_pythonhosted.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cluttrdev/pytrajectory/71deb2a8ae912bea1223da799f57be49cae65ed0/doc/pythonhosted_files/PyTrajectory_1.2.0_pythonhosted.zip -------------------------------------------------------------------------------- /doc/pythonhosted_files/_static/basic.css: -------------------------------------------------------------------------------- 1 | /* 2 | * basic.css 3 | * ~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- basic theme. 6 | * 7 | * :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /* -- main layout ----------------------------------------------------------- */ 13 | 14 | div.clearer { 15 | clear: both; 16 | } 17 | 18 | /* -- relbar ---------------------------------------------------------------- */ 19 | 20 | div.related { 21 | width: 100%; 22 | font-size: 90%; 23 | } 24 | 25 | div.related h3 { 26 | display: none; 27 | } 28 | 29 | div.related ul { 30 | margin: 0; 31 | padding: 0 0 0 10px; 32 | list-style: none; 33 | } 34 | 35 | div.related li { 36 | display: inline; 37 | } 38 | 39 | div.related li.right { 40 | float: right; 41 | margin-right: 5px; 42 | } 43 | 44 | /* -- sidebar --------------------------------------------------------------- */ 45 | 46 | div.sphinxsidebarwrapper { 47 | padding: 10px 5px 0 10px; 48 | } 49 | 50 | div.sphinxsidebar { 51 | float: left; 52 | width: 230px; 53 | margin-left: -100%; 54 | font-size: 90%; 55 | } 56 | 57 | div.sphinxsidebar ul { 58 | list-style: none; 59 | } 60 | 61 | div.sphinxsidebar ul ul, 62 | div.sphinxsidebar ul.want-points { 63 | margin-left: 20px; 64 | list-style: square; 65 | } 66 | 67 | div.sphinxsidebar ul ul { 68 | margin-top: 0; 69 | margin-bottom: 0; 70 | } 71 | 72 | div.sphinxsidebar form { 73 | margin-top: 10px; 74 | } 75 | 76 | div.sphinxsidebar input { 77 | border: 1px solid #98dbcc; 78 | font-family: sans-serif; 79 | font-size: 1em; 80 | } 81 | 82 | div.sphinxsidebar #searchbox input[type="text"] { 83 | width: 170px; 84 | } 85 | 86 | div.sphinxsidebar #searchbox input[type="submit"] { 87 | width: 30px; 88 | } 89 | 90 | img { 91 | border: 0; 92 | max-width: 100%; 93 | } 94 | 95 | /* -- search page ----------------------------------------------------------- */ 96 | 97 | ul.search { 98 | margin: 10px 0 0 20px; 99 | padding: 0; 100 | } 101 | 102 | ul.search li { 103 | padding: 5px 0 5px 20px; 104 | background-image: url(file.png); 105 | background-repeat: no-repeat; 106 | background-position: 0 7px; 107 | } 108 | 109 | ul.search li a { 110 | font-weight: bold; 111 | } 112 | 113 | ul.search li div.context { 114 | color: #888; 115 | margin: 2px 0 0 30px; 116 | text-align: left; 117 | } 118 | 119 | ul.keywordmatches li.goodmatch a { 120 | font-weight: bold; 121 | } 122 | 123 | /* -- index page ------------------------------------------------------------ */ 124 | 125 | table.contentstable { 126 | width: 90%; 127 | } 128 | 129 | table.contentstable p.biglink { 130 | line-height: 150%; 131 | } 132 | 133 | a.biglink { 134 | font-size: 1.3em; 135 | } 136 | 137 | span.linkdescr { 138 | font-style: italic; 139 | padding-top: 5px; 140 | font-size: 90%; 141 | } 142 | 143 | /* -- general index --------------------------------------------------------- */ 144 | 145 | table.indextable { 146 | width: 100%; 147 | } 148 | 149 | table.indextable td { 150 | text-align: left; 151 | vertical-align: top; 152 | } 153 | 154 | table.indextable dl, table.indextable dd { 155 | margin-top: 0; 156 | margin-bottom: 0; 157 | } 158 | 159 | table.indextable tr.pcap { 160 | height: 10px; 161 | } 162 | 163 | table.indextable tr.cap { 164 | margin-top: 10px; 165 | background-color: #f2f2f2; 166 | } 167 | 168 | img.toggler { 169 | margin-right: 3px; 170 | margin-top: 3px; 171 | cursor: pointer; 172 | } 173 | 174 | div.modindex-jumpbox { 175 | border-top: 1px solid #ddd; 176 | border-bottom: 1px solid #ddd; 177 | margin: 1em 0 1em 0; 178 | padding: 0.4em; 179 | } 180 | 181 | div.genindex-jumpbox { 182 | border-top: 1px solid #ddd; 183 | border-bottom: 1px solid #ddd; 184 | margin: 1em 0 1em 0; 185 | padding: 0.4em; 186 | } 187 | 188 | /* -- general body styles --------------------------------------------------- */ 189 | 190 | a.headerlink { 191 | visibility: hidden; 192 | } 193 | 194 | h1:hover > a.headerlink, 195 | h2:hover > a.headerlink, 196 | h3:hover > a.headerlink, 197 | h4:hover > a.headerlink, 198 | h5:hover > a.headerlink, 199 | h6:hover > a.headerlink, 200 | dt:hover > a.headerlink { 201 | visibility: visible; 202 | } 203 | 204 | div.body p.caption { 205 | text-align: inherit; 206 | } 207 | 208 | div.body td { 209 | text-align: left; 210 | } 211 | 212 | .field-list ul { 213 | padding-left: 1em; 214 | } 215 | 216 | .first { 217 | margin-top: 0 !important; 218 | } 219 | 220 | p.rubric { 221 | margin-top: 30px; 222 | font-weight: bold; 223 | } 224 | 225 | img.align-left, .figure.align-left, object.align-left { 226 | clear: left; 227 | float: left; 228 | margin-right: 1em; 229 | } 230 | 231 | img.align-right, .figure.align-right, object.align-right { 232 | clear: right; 233 | float: right; 234 | margin-left: 1em; 235 | } 236 | 237 | img.align-center, .figure.align-center, object.align-center { 238 | display: block; 239 | margin-left: auto; 240 | margin-right: auto; 241 | } 242 | 243 | .align-left { 244 | text-align: left; 245 | } 246 | 247 | .align-center { 248 | text-align: center; 249 | } 250 | 251 | .align-right { 252 | text-align: right; 253 | } 254 | 255 | /* -- sidebars -------------------------------------------------------------- */ 256 | 257 | div.sidebar { 258 | margin: 0 0 0.5em 1em; 259 | border: 1px solid #ddb; 260 | padding: 7px 7px 0 7px; 261 | background-color: #ffe; 262 | width: 40%; 263 | float: right; 264 | } 265 | 266 | p.sidebar-title { 267 | font-weight: bold; 268 | } 269 | 270 | /* -- topics ---------------------------------------------------------------- */ 271 | 272 | div.topic { 273 | border: 1px solid #ccc; 274 | padding: 7px 7px 0 7px; 275 | margin: 10px 0 10px 0; 276 | } 277 | 278 | p.topic-title { 279 | font-size: 1.1em; 280 | font-weight: bold; 281 | margin-top: 10px; 282 | } 283 | 284 | /* -- admonitions ----------------------------------------------------------- */ 285 | 286 | div.admonition { 287 | margin-top: 10px; 288 | margin-bottom: 10px; 289 | padding: 7px; 290 | } 291 | 292 | div.admonition dt { 293 | font-weight: bold; 294 | } 295 | 296 | div.admonition dl { 297 | margin-bottom: 0; 298 | } 299 | 300 | p.admonition-title { 301 | margin: 0px 10px 5px 0px; 302 | font-weight: bold; 303 | } 304 | 305 | div.body p.centered { 306 | text-align: center; 307 | margin-top: 25px; 308 | } 309 | 310 | /* -- tables ---------------------------------------------------------------- */ 311 | 312 | table.docutils { 313 | border: 0; 314 | border-collapse: collapse; 315 | } 316 | 317 | table.docutils td, table.docutils th { 318 | padding: 1px 8px 1px 5px; 319 | border-top: 0; 320 | border-left: 0; 321 | border-right: 0; 322 | border-bottom: 1px solid #aaa; 323 | } 324 | 325 | table.field-list td, table.field-list th { 326 | border: 0 !important; 327 | } 328 | 329 | table.footnote td, table.footnote th { 330 | border: 0 !important; 331 | } 332 | 333 | th { 334 | text-align: left; 335 | padding-right: 5px; 336 | } 337 | 338 | table.citation { 339 | border-left: solid 1px gray; 340 | margin-left: 1px; 341 | } 342 | 343 | table.citation td { 344 | border-bottom: none; 345 | } 346 | 347 | /* -- other body styles ----------------------------------------------------- */ 348 | 349 | ol.arabic { 350 | list-style: decimal; 351 | } 352 | 353 | ol.loweralpha { 354 | list-style: lower-alpha; 355 | } 356 | 357 | ol.upperalpha { 358 | list-style: upper-alpha; 359 | } 360 | 361 | ol.lowerroman { 362 | list-style: lower-roman; 363 | } 364 | 365 | ol.upperroman { 366 | list-style: upper-roman; 367 | } 368 | 369 | dl { 370 | margin-bottom: 15px; 371 | } 372 | 373 | dd p { 374 | margin-top: 0px; 375 | } 376 | 377 | dd ul, dd table { 378 | margin-bottom: 10px; 379 | } 380 | 381 | dd { 382 | margin-top: 3px; 383 | margin-bottom: 10px; 384 | margin-left: 30px; 385 | } 386 | 387 | dt:target, .highlighted { 388 | background-color: #fbe54e; 389 | } 390 | 391 | dl.glossary dt { 392 | font-weight: bold; 393 | font-size: 1.1em; 394 | } 395 | 396 | .field-list ul { 397 | margin: 0; 398 | padding-left: 1em; 399 | } 400 | 401 | .field-list p { 402 | margin: 0; 403 | } 404 | 405 | .optional { 406 | font-size: 1.3em; 407 | } 408 | 409 | .versionmodified { 410 | font-style: italic; 411 | } 412 | 413 | .system-message { 414 | background-color: #fda; 415 | padding: 5px; 416 | border: 3px solid red; 417 | } 418 | 419 | .footnote:target { 420 | background-color: #ffa; 421 | } 422 | 423 | .line-block { 424 | display: block; 425 | margin-top: 1em; 426 | margin-bottom: 1em; 427 | } 428 | 429 | .line-block .line-block { 430 | margin-top: 0; 431 | margin-bottom: 0; 432 | margin-left: 1.5em; 433 | } 434 | 435 | .guilabel, .menuselection { 436 | font-family: sans-serif; 437 | } 438 | 439 | .accelerator { 440 | text-decoration: underline; 441 | } 442 | 443 | .classifier { 444 | font-style: oblique; 445 | } 446 | 447 | abbr, acronym { 448 | border-bottom: dotted 1px; 449 | cursor: help; 450 | } 451 | 452 | /* -- code displays --------------------------------------------------------- */ 453 | 454 | pre { 455 | overflow: auto; 456 | overflow-y: hidden; /* fixes display issues on Chrome browsers */ 457 | } 458 | 459 | td.linenos pre { 460 | padding: 5px 0px; 461 | border: 0; 462 | background-color: transparent; 463 | color: #aaa; 464 | } 465 | 466 | table.highlighttable { 467 | margin-left: 0.5em; 468 | } 469 | 470 | table.highlighttable td { 471 | padding: 0 0.5em 0 0.5em; 472 | } 473 | 474 | tt.descname { 475 | background-color: transparent; 476 | font-weight: bold; 477 | font-size: 1.2em; 478 | } 479 | 480 | tt.descclassname { 481 | background-color: transparent; 482 | } 483 | 484 | tt.xref, a tt { 485 | background-color: transparent; 486 | font-weight: bold; 487 | } 488 | 489 | h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { 490 | background-color: transparent; 491 | } 492 | 493 | .viewcode-link { 494 | float: right; 495 | } 496 | 497 | .viewcode-back { 498 | float: right; 499 | font-family: sans-serif; 500 | } 501 | 502 | div.viewcode-block:target { 503 | margin: -1px -10px; 504 | padding: 0 10px; 505 | } 506 | 507 | /* -- math display ---------------------------------------------------------- */ 508 | 509 | img.math { 510 | vertical-align: middle; 511 | } 512 | 513 | div.body div.math p { 514 | text-align: center; 515 | } 516 | 517 | span.eqno { 518 | float: right; 519 | } 520 | 521 | /* -- printout stylesheet --------------------------------------------------- */ 522 | 523 | @media print { 524 | div.document, 525 | div.documentwrapper, 526 | div.bodywrapper { 527 | margin: 0 !important; 528 | width: 100%; 529 | } 530 | 531 | div.sphinxsidebar, 532 | div.related, 533 | div.footer, 534 | #top-link { 535 | display: none; 536 | } 537 | } -------------------------------------------------------------------------------- /doc/pythonhosted_files/_static/default.css: -------------------------------------------------------------------------------- 1 | /* 2 | * default.css_t 3 | * ~~~~~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- default theme. 6 | * 7 | * :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | @import url("basic.css"); 13 | 14 | /* -- page layout ----------------------------------------------------------- */ 15 | 16 | body { 17 | font-family: sans-serif; 18 | font-size: 100%; 19 | background-color: #11303d; 20 | color: #000; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | div.document { 26 | background-color: #1c4e63; 27 | } 28 | 29 | div.documentwrapper { 30 | float: left; 31 | width: 100%; 32 | } 33 | 34 | div.bodywrapper { 35 | margin: 0 0 0 230px; 36 | } 37 | 38 | div.body { 39 | background-color: #ffffff; 40 | color: #000000; 41 | padding: 0 20px 30px 20px; 42 | } 43 | 44 | div.footer { 45 | color: #ffffff; 46 | width: 100%; 47 | padding: 9px 0 9px 0; 48 | text-align: center; 49 | font-size: 75%; 50 | } 51 | 52 | div.footer a { 53 | color: #ffffff; 54 | text-decoration: underline; 55 | } 56 | 57 | div.related { 58 | background-color: #133f52; 59 | line-height: 30px; 60 | color: #ffffff; 61 | } 62 | 63 | div.related a { 64 | color: #ffffff; 65 | } 66 | 67 | div.sphinxsidebar { 68 | } 69 | 70 | div.sphinxsidebar h3 { 71 | font-family: 'Trebuchet MS', sans-serif; 72 | color: #ffffff; 73 | font-size: 1.4em; 74 | font-weight: normal; 75 | margin: 0; 76 | padding: 0; 77 | } 78 | 79 | div.sphinxsidebar h3 a { 80 | color: #ffffff; 81 | } 82 | 83 | div.sphinxsidebar h4 { 84 | font-family: 'Trebuchet MS', sans-serif; 85 | color: #ffffff; 86 | font-size: 1.3em; 87 | font-weight: normal; 88 | margin: 5px 0 0 0; 89 | padding: 0; 90 | } 91 | 92 | div.sphinxsidebar p { 93 | color: #ffffff; 94 | } 95 | 96 | div.sphinxsidebar p.topless { 97 | margin: 5px 10px 10px 10px; 98 | } 99 | 100 | div.sphinxsidebar ul { 101 | margin: 10px; 102 | padding: 0; 103 | color: #ffffff; 104 | } 105 | 106 | div.sphinxsidebar a { 107 | color: #98dbcc; 108 | } 109 | 110 | div.sphinxsidebar input { 111 | border: 1px solid #98dbcc; 112 | font-family: sans-serif; 113 | font-size: 1em; 114 | } 115 | 116 | 117 | 118 | /* -- hyperlink styles ------------------------------------------------------ */ 119 | 120 | a { 121 | color: #355f7c; 122 | text-decoration: none; 123 | } 124 | 125 | a:visited { 126 | color: #355f7c; 127 | text-decoration: none; 128 | } 129 | 130 | a:hover { 131 | text-decoration: underline; 132 | } 133 | 134 | 135 | 136 | /* -- body styles ----------------------------------------------------------- */ 137 | 138 | div.body h1, 139 | div.body h2, 140 | div.body h3, 141 | div.body h4, 142 | div.body h5, 143 | div.body h6 { 144 | font-family: 'Trebuchet MS', sans-serif; 145 | background-color: #f2f2f2; 146 | font-weight: normal; 147 | color: #20435c; 148 | border-bottom: 1px solid #ccc; 149 | margin: 20px -20px 10px -20px; 150 | padding: 3px 0 3px 10px; 151 | } 152 | 153 | div.body h1 { margin-top: 0; font-size: 200%; } 154 | div.body h2 { font-size: 160%; } 155 | div.body h3 { font-size: 140%; } 156 | div.body h4 { font-size: 120%; } 157 | div.body h5 { font-size: 110%; } 158 | div.body h6 { font-size: 100%; } 159 | 160 | a.headerlink { 161 | color: #c60f0f; 162 | font-size: 0.8em; 163 | padding: 0 4px 0 4px; 164 | text-decoration: none; 165 | } 166 | 167 | a.headerlink:hover { 168 | background-color: #c60f0f; 169 | color: white; 170 | } 171 | 172 | div.body p, div.body dd, div.body li { 173 | text-align: justify; 174 | line-height: 130%; 175 | } 176 | 177 | div.admonition p.admonition-title + p { 178 | display: inline; 179 | } 180 | 181 | div.admonition p { 182 | margin-bottom: 5px; 183 | } 184 | 185 | div.admonition pre { 186 | margin-bottom: 5px; 187 | } 188 | 189 | div.admonition ul, div.admonition ol { 190 | margin-bottom: 5px; 191 | } 192 | 193 | div.note { 194 | background-color: #eee; 195 | border: 1px solid #ccc; 196 | } 197 | 198 | div.seealso { 199 | background-color: #ffc; 200 | border: 1px solid #ff6; 201 | } 202 | 203 | div.topic { 204 | background-color: #eee; 205 | } 206 | 207 | div.warning { 208 | background-color: #ffe4e4; 209 | border: 1px solid #f66; 210 | } 211 | 212 | p.admonition-title { 213 | display: inline; 214 | } 215 | 216 | p.admonition-title:after { 217 | content: ":"; 218 | } 219 | 220 | pre { 221 | padding: 5px; 222 | background-color: #eeffcc; 223 | color: #333333; 224 | line-height: 120%; 225 | border: 1px solid #ac9; 226 | border-left: none; 227 | border-right: none; 228 | } 229 | 230 | tt { 231 | background-color: #ecf0f3; 232 | padding: 0 1px 0 1px; 233 | font-size: 0.95em; 234 | } 235 | 236 | th { 237 | background-color: #ede; 238 | } 239 | 240 | .warning tt { 241 | background: #efc2c2; 242 | } 243 | 244 | .note tt { 245 | background: #d6d6d6; 246 | } 247 | 248 | .viewcode-back { 249 | font-family: sans-serif; 250 | } 251 | 252 | div.viewcode-block:target { 253 | background-color: #f4debf; 254 | border-top: 1px solid #ac9; 255 | border-bottom: 1px solid #ac9; 256 | } -------------------------------------------------------------------------------- /doc/pythonhosted_files/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * doctools.js 3 | * ~~~~~~~~~~~ 4 | * 5 | * Sphinx JavaScript utilities for all documentation. 6 | * 7 | * :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /** 13 | * select a different prefix for underscore 14 | */ 15 | $u = _.noConflict(); 16 | 17 | /** 18 | * make the code below compatible with browsers without 19 | * an installed firebug like debugger 20 | if (!window.console || !console.firebug) { 21 | var names = ["log", "debug", "info", "warn", "error", "assert", "dir", 22 | "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", 23 | "profile", "profileEnd"]; 24 | window.console = {}; 25 | for (var i = 0; i < names.length; ++i) 26 | window.console[names[i]] = function() {}; 27 | } 28 | */ 29 | 30 | /** 31 | * small helper function to urldecode strings 32 | */ 33 | jQuery.urldecode = function(x) { 34 | return decodeURIComponent(x).replace(/\+/g, ' '); 35 | }; 36 | 37 | /** 38 | * small helper function to urlencode strings 39 | */ 40 | jQuery.urlencode = encodeURIComponent; 41 | 42 | /** 43 | * This function returns the parsed url parameters of the 44 | * current request. Multiple values per key are supported, 45 | * it will always return arrays of strings for the value parts. 46 | */ 47 | jQuery.getQueryParameters = function(s) { 48 | if (typeof s == 'undefined') 49 | s = document.location.search; 50 | var parts = s.substr(s.indexOf('?') + 1).split('&'); 51 | var result = {}; 52 | for (var i = 0; i < parts.length; i++) { 53 | var tmp = parts[i].split('=', 2); 54 | var key = jQuery.urldecode(tmp[0]); 55 | var value = jQuery.urldecode(tmp[1]); 56 | if (key in result) 57 | result[key].push(value); 58 | else 59 | result[key] = [value]; 60 | } 61 | return result; 62 | }; 63 | 64 | /** 65 | * highlight a given string on a jquery object by wrapping it in 66 | * span elements with the given class name. 67 | */ 68 | jQuery.fn.highlightText = function(text, className) { 69 | function highlight(node) { 70 | if (node.nodeType == 3) { 71 | var val = node.nodeValue; 72 | var pos = val.toLowerCase().indexOf(text); 73 | if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { 74 | var span = document.createElement("span"); 75 | span.className = className; 76 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 77 | node.parentNode.insertBefore(span, node.parentNode.insertBefore( 78 | document.createTextNode(val.substr(pos + text.length)), 79 | node.nextSibling)); 80 | node.nodeValue = val.substr(0, pos); 81 | } 82 | } 83 | else if (!jQuery(node).is("button, select, textarea")) { 84 | jQuery.each(node.childNodes, function() { 85 | highlight(this); 86 | }); 87 | } 88 | } 89 | return this.each(function() { 90 | highlight(this); 91 | }); 92 | }; 93 | 94 | /** 95 | * Small JavaScript module for the documentation. 96 | */ 97 | var Documentation = { 98 | 99 | init : function() { 100 | this.fixFirefoxAnchorBug(); 101 | this.highlightSearchWords(); 102 | this.initIndexTable(); 103 | }, 104 | 105 | /** 106 | * i18n support 107 | */ 108 | TRANSLATIONS : {}, 109 | PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, 110 | LOCALE : 'unknown', 111 | 112 | // gettext and ngettext don't access this so that the functions 113 | // can safely bound to a different name (_ = Documentation.gettext) 114 | gettext : function(string) { 115 | var translated = Documentation.TRANSLATIONS[string]; 116 | if (typeof translated == 'undefined') 117 | return string; 118 | return (typeof translated == 'string') ? translated : translated[0]; 119 | }, 120 | 121 | ngettext : function(singular, plural, n) { 122 | var translated = Documentation.TRANSLATIONS[singular]; 123 | if (typeof translated == 'undefined') 124 | return (n == 1) ? singular : plural; 125 | return translated[Documentation.PLURALEXPR(n)]; 126 | }, 127 | 128 | addTranslations : function(catalog) { 129 | for (var key in catalog.messages) 130 | this.TRANSLATIONS[key] = catalog.messages[key]; 131 | this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); 132 | this.LOCALE = catalog.locale; 133 | }, 134 | 135 | /** 136 | * add context elements like header anchor links 137 | */ 138 | addContextElements : function() { 139 | $('div[id] > :header:first').each(function() { 140 | $('\u00B6'). 141 | attr('href', '#' + this.id). 142 | attr('title', _('Permalink to this headline')). 143 | appendTo(this); 144 | }); 145 | $('dt[id]').each(function() { 146 | $('\u00B6'). 147 | attr('href', '#' + this.id). 148 | attr('title', _('Permalink to this definition')). 149 | appendTo(this); 150 | }); 151 | }, 152 | 153 | /** 154 | * workaround a firefox stupidity 155 | */ 156 | fixFirefoxAnchorBug : function() { 157 | if (document.location.hash && $.browser.mozilla) 158 | window.setTimeout(function() { 159 | document.location.href += ''; 160 | }, 10); 161 | }, 162 | 163 | /** 164 | * highlight the search words provided in the url in the text 165 | */ 166 | highlightSearchWords : function() { 167 | var params = $.getQueryParameters(); 168 | var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; 169 | if (terms.length) { 170 | var body = $('div.body'); 171 | if (!body.length) { 172 | body = $('body'); 173 | } 174 | window.setTimeout(function() { 175 | $.each(terms, function() { 176 | body.highlightText(this.toLowerCase(), 'highlighted'); 177 | }); 178 | }, 10); 179 | $('') 181 | .appendTo($('#searchbox')); 182 | } 183 | }, 184 | 185 | /** 186 | * init the domain index toggle buttons 187 | */ 188 | initIndexTable : function() { 189 | var togglers = $('img.toggler').click(function() { 190 | var src = $(this).attr('src'); 191 | var idnum = $(this).attr('id').substr(7); 192 | $('tr.cg-' + idnum).toggle(); 193 | if (src.substr(-9) == 'minus.png') 194 | $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); 195 | else 196 | $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); 197 | }).css('display', ''); 198 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { 199 | togglers.click(); 200 | } 201 | }, 202 | 203 | /** 204 | * helper function to hide the search marks again 205 | */ 206 | hideSearchWords : function() { 207 | $('#searchbox .highlight-link').fadeOut(300); 208 | $('span.highlighted').removeClass('highlighted'); 209 | }, 210 | 211 | /** 212 | * make the url absolute 213 | */ 214 | makeURL : function(relativeURL) { 215 | return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; 216 | }, 217 | 218 | /** 219 | * get the current relative url 220 | */ 221 | getCurrentURL : function() { 222 | var path = document.location.pathname; 223 | var parts = path.split(/\//); 224 | $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { 225 | if (this == '..') 226 | parts.pop(); 227 | }); 228 | var url = parts.join('/'); 229 | return path.substring(url.lastIndexOf('/') + 1, path.length - 1); 230 | } 231 | }; 232 | 233 | // quick alias for translations 234 | _ = Documentation.gettext; 235 | 236 | $(document).ready(function() { 237 | Documentation.init(); 238 | }); 239 | -------------------------------------------------------------------------------- /doc/pythonhosted_files/_static/pygments.css: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #ffffcc } 2 | .highlight { background: #eeffcc; } 3 | .highlight .c { color: #408090; font-style: italic } /* Comment */ 4 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 5 | .highlight .k { color: #007020; font-weight: bold } /* Keyword */ 6 | .highlight .o { color: #666666 } /* Operator */ 7 | .highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ 8 | .highlight .cp { color: #007020 } /* Comment.Preproc */ 9 | .highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ 10 | .highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ 11 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 12 | .highlight .ge { font-style: italic } /* Generic.Emph */ 13 | .highlight .gr { color: #FF0000 } /* Generic.Error */ 14 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 15 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 16 | .highlight .go { color: #333333 } /* Generic.Output */ 17 | .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 18 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 19 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 20 | .highlight .gt { color: #0044DD } /* Generic.Traceback */ 21 | .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ 22 | .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ 23 | .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ 24 | .highlight .kp { color: #007020 } /* Keyword.Pseudo */ 25 | .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ 26 | .highlight .kt { color: #902000 } /* Keyword.Type */ 27 | .highlight .m { color: #208050 } /* Literal.Number */ 28 | .highlight .s { color: #4070a0 } /* Literal.String */ 29 | .highlight .na { color: #4070a0 } /* Name.Attribute */ 30 | .highlight .nb { color: #007020 } /* Name.Builtin */ 31 | .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ 32 | .highlight .no { color: #60add5 } /* Name.Constant */ 33 | .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ 34 | .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ 35 | .highlight .ne { color: #007020 } /* Name.Exception */ 36 | .highlight .nf { color: #06287e } /* Name.Function */ 37 | .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ 38 | .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 39 | .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ 40 | .highlight .nv { color: #bb60d5 } /* Name.Variable */ 41 | .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ 42 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 43 | .highlight .mb { color: #208050 } /* Literal.Number.Bin */ 44 | .highlight .mf { color: #208050 } /* Literal.Number.Float */ 45 | .highlight .mh { color: #208050 } /* Literal.Number.Hex */ 46 | .highlight .mi { color: #208050 } /* Literal.Number.Integer */ 47 | .highlight .mo { color: #208050 } /* Literal.Number.Oct */ 48 | .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ 49 | .highlight .sc { color: #4070a0 } /* Literal.String.Char */ 50 | .highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ 51 | .highlight .s2 { color: #4070a0 } /* Literal.String.Double */ 52 | .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ 53 | .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ 54 | .highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ 55 | .highlight .sx { color: #c65d09 } /* Literal.String.Other */ 56 | .highlight .sr { color: #235388 } /* Literal.String.Regex */ 57 | .highlight .s1 { color: #4070a0 } /* Literal.String.Single */ 58 | .highlight .ss { color: #517918 } /* Literal.String.Symbol */ 59 | .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ 60 | .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ 61 | .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ 62 | .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ 63 | .highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /doc/pythonhosted_files/_static/sidebar.js: -------------------------------------------------------------------------------- 1 | /* 2 | * sidebar.js 3 | * ~~~~~~~~~~ 4 | * 5 | * This script makes the Sphinx sidebar collapsible. 6 | * 7 | * .sphinxsidebar contains .sphinxsidebarwrapper. This script adds 8 | * in .sphixsidebar, after .sphinxsidebarwrapper, the #sidebarbutton 9 | * used to collapse and expand the sidebar. 10 | * 11 | * When the sidebar is collapsed the .sphinxsidebarwrapper is hidden 12 | * and the width of the sidebar and the margin-left of the document 13 | * are decreased. When the sidebar is expanded the opposite happens. 14 | * This script saves a per-browser/per-session cookie used to 15 | * remember the position of the sidebar among the pages. 16 | * Once the browser is closed the cookie is deleted and the position 17 | * reset to the default (expanded). 18 | * 19 | * :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. 20 | * :license: BSD, see LICENSE for details. 21 | * 22 | */ 23 | 24 | $(function() { 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | // global elements used by the functions. 34 | // the 'sidebarbutton' element is defined as global after its 35 | // creation, in the add_sidebar_button function 36 | var bodywrapper = $('.bodywrapper'); 37 | var sidebar = $('.sphinxsidebar'); 38 | var sidebarwrapper = $('.sphinxsidebarwrapper'); 39 | 40 | // for some reason, the document has no sidebar; do not run into errors 41 | if (!sidebar.length) return; 42 | 43 | // original margin-left of the bodywrapper and width of the sidebar 44 | // with the sidebar expanded 45 | var bw_margin_expanded = bodywrapper.css('margin-left'); 46 | var ssb_width_expanded = sidebar.width(); 47 | 48 | // margin-left of the bodywrapper and width of the sidebar 49 | // with the sidebar collapsed 50 | var bw_margin_collapsed = '.8em'; 51 | var ssb_width_collapsed = '.8em'; 52 | 53 | // colors used by the current theme 54 | var dark_color = $('.related').css('background-color'); 55 | var light_color = $('.document').css('background-color'); 56 | 57 | function sidebar_is_collapsed() { 58 | return sidebarwrapper.is(':not(:visible)'); 59 | } 60 | 61 | function toggle_sidebar() { 62 | if (sidebar_is_collapsed()) 63 | expand_sidebar(); 64 | else 65 | collapse_sidebar(); 66 | } 67 | 68 | function collapse_sidebar() { 69 | sidebarwrapper.hide(); 70 | sidebar.css('width', ssb_width_collapsed); 71 | bodywrapper.css('margin-left', bw_margin_collapsed); 72 | sidebarbutton.css({ 73 | 'margin-left': '0', 74 | 'height': bodywrapper.height() 75 | }); 76 | sidebarbutton.find('span').text('»'); 77 | sidebarbutton.attr('title', _('Expand sidebar')); 78 | document.cookie = 'sidebar=collapsed'; 79 | } 80 | 81 | function expand_sidebar() { 82 | bodywrapper.css('margin-left', bw_margin_expanded); 83 | sidebar.css('width', ssb_width_expanded); 84 | sidebarwrapper.show(); 85 | sidebarbutton.css({ 86 | 'margin-left': ssb_width_expanded-12, 87 | 'height': bodywrapper.height() 88 | }); 89 | sidebarbutton.find('span').text('«'); 90 | sidebarbutton.attr('title', _('Collapse sidebar')); 91 | document.cookie = 'sidebar=expanded'; 92 | } 93 | 94 | function add_sidebar_button() { 95 | sidebarwrapper.css({ 96 | 'float': 'left', 97 | 'margin-right': '0', 98 | 'width': ssb_width_expanded - 28 99 | }); 100 | // create the button 101 | sidebar.append( 102 | '
«
' 103 | ); 104 | var sidebarbutton = $('#sidebarbutton'); 105 | light_color = sidebarbutton.css('background-color'); 106 | // find the height of the viewport to center the '<<' in the page 107 | var viewport_height; 108 | if (window.innerHeight) 109 | viewport_height = window.innerHeight; 110 | else 111 | viewport_height = $(window).height(); 112 | sidebarbutton.find('span').css({ 113 | 'display': 'block', 114 | 'margin-top': (viewport_height - sidebar.position().top - 20) / 2 115 | }); 116 | 117 | sidebarbutton.click(toggle_sidebar); 118 | sidebarbutton.attr('title', _('Collapse sidebar')); 119 | sidebarbutton.css({ 120 | 'color': '#FFFFFF', 121 | 'border-left': '1px solid ' + dark_color, 122 | 'font-size': '1.2em', 123 | 'cursor': 'pointer', 124 | 'height': bodywrapper.height(), 125 | 'padding-top': '1px', 126 | 'margin-left': ssb_width_expanded - 12 127 | }); 128 | 129 | sidebarbutton.hover( 130 | function () { 131 | $(this).css('background-color', dark_color); 132 | }, 133 | function () { 134 | $(this).css('background-color', light_color); 135 | } 136 | ); 137 | } 138 | 139 | function set_position_from_cookie() { 140 | if (!document.cookie) 141 | return; 142 | var items = document.cookie.split(';'); 143 | for(var k=0; k2;a== 12 | null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect= 13 | function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e= 14 | e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck= 15 | function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;bd?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a, 17 | c,d){d||(d=b.identity);for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}}; 24 | b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments, 25 | 1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)}; 26 | b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"}; 27 | b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a), 28 | function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+ 29 | u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]= 30 | function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain= 31 | true;return this};m.prototype.value=function(){return this._wrapped}}).call(this); 32 | -------------------------------------------------------------------------------- /doc/pythonhosted_files/index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Welcome to PyTrajectory’s documentation! — pytrajectory 1.2.0 documentation 10 | 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 35 | 36 |
37 |
38 |
39 |
40 | 41 |
42 |

Welcome to PyTrajectory’s documentation!

43 | 44 | The latest documentation is hosted at readthedocs.org 45 | 46 | 47 |
48 |
49 |
50 |
51 | 52 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /doc/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # pytrajectory documentation build configuration file, created by 4 | # sphinx-quickstart2 on Sun Mar 23 14:31:12 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | 18 | # If extensions (or modules to document with autodoc) are in another directory, 19 | # add these directories to sys.path here. If the directory is relative to the 20 | # documentation root, use os.path.abspath to make it absolute, like shown here. 21 | sys.path.insert(0, os.path.abspath('./../..')) 22 | sys.path.insert(0, os.path.abspath('./../../pytrajectory')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [ 33 | 'sphinx.ext.autodoc', 34 | 'sphinx.ext.doctest', 35 | 'sphinx.ext.todo', 36 | 'sphinx.ext.pngmath', 37 | # 'sphinx.ext.mathjax', 38 | 'sphinx.ext.viewcode' 39 | ] 40 | 41 | # add napoleon to the extension to be able to write cleaner docstrings 42 | import sphinx 43 | if sphinx.version_info[0] <= 1 and sphinx.version_info[1] <= 2: 44 | # up to version 1.2 napoleon is not part of sphinx extensions 45 | extensions.append('sphinxcontrib.napoleon') 46 | else: 47 | # from version 1.3 onwards napoleon is part of the extensions 48 | extensions.append('sphinx.ext.napoleon') 49 | 50 | # Add any paths that contain templates here, relative to this directory. 51 | templates_path = ['_templates'] 52 | 53 | # The suffix of source filenames. 54 | source_suffix = '.rst' 55 | 56 | # The encoding of source files. 57 | #source_encoding = 'utf-8-sig' 58 | 59 | # The master toctree document. 60 | master_doc = 'index' 61 | 62 | # General information about the project. 63 | project = u'pytrajectory' 64 | copyright = u'2015, Andreas Kunze' 65 | 66 | # The version info for the project you're documenting, acts as replacement for 67 | # |version| and |release|, also used in various other places throughout the 68 | # built documents. 69 | # 70 | import pytrajectory 71 | 72 | # The short X.Y version. 73 | version = pytrajectory.__version__ 74 | # The full version, including alpha/beta/rc tags. 75 | #release = pytrajectory.__release__ 76 | release = pytrajectory.__version__ 77 | 78 | # The language for content autogenerated by Sphinx. Refer to documentation 79 | # for a list of supported languages. 80 | #language = None 81 | 82 | # There are two options for replacing |today|: either, you set today to some 83 | # non-false value, then it is used: 84 | #today = '' 85 | # Else, today_fmt is used as the format for a strftime call. 86 | #today_fmt = '%B %d, %Y' 87 | 88 | # List of patterns, relative to source directory, that match files and 89 | # directories to ignore when looking for source files. 90 | exclude_patterns = [] 91 | 92 | # The reST default role (used for this markup: `text`) to use for all 93 | # documents. 94 | #default_role = None 95 | 96 | # If true, '()' will be appended to :func: etc. cross-reference text. 97 | add_function_parentheses = False 98 | 99 | # If true, the current module name will be prepended to all description 100 | # unit titles (such as .. function::). 101 | #add_module_names = True 102 | 103 | # If true, sectionauthor and moduleauthor directives will be shown in the 104 | # output. They are ignored by default. 105 | #show_authors = False 106 | 107 | # The name of the Pygments (syntax highlighting) style to use. 108 | pygments_style = 'sphinx' 109 | 110 | # A list of ignored prefixes for module index sorting. 111 | #modindex_common_prefix = [] 112 | 113 | # If true, keep warnings as "system message" paragraphs in the built documents. 114 | #keep_warnings = False 115 | 116 | 117 | # -- Options for HTML output ---------------------------------------------- 118 | 119 | # The theme to use for HTML and HTML Help pages. See the documentation for 120 | # a list of builtin themes. 121 | # new: alabaster 122 | # old default: classic 123 | #html_theme = 'classic' 124 | html_theme = 'default' 125 | 126 | # Theme options are theme-specific and customize the look and feel of a theme 127 | # further. For a list of options available for each theme, see the 128 | # documentation. 129 | #html_theme_options = {} 130 | 131 | # Add any paths that contain custom themes here, relative to this directory. 132 | #html_theme_path = [] 133 | 134 | # The name for this set of Sphinx documents. If None, it defaults to 135 | # " v documentation". 136 | #html_title = None 137 | 138 | # A shorter title for the navigation bar. Default is the same as html_title. 139 | #html_short_title = None 140 | 141 | # The name of an image file (relative to this directory) to place at the top 142 | # of the sidebar. 143 | #html_logo = None 144 | 145 | # The name of an image file (within the static path) to use as favicon of the 146 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 147 | # pixels large. 148 | #html_favicon = None 149 | 150 | # Add any paths that contain custom static files (such as style sheets) here, 151 | # relative to this directory. They are copied after the builtin static files, 152 | # so a file named "default.css" will overwrite the builtin "default.css". 153 | html_static_path = ['_static'] 154 | 155 | # Add any extra paths that contain custom files (such as robots.txt or 156 | # .htaccess) here, relative to this directory. These files are copied 157 | # directly to the root of the documentation. 158 | #html_extra_path = [] 159 | 160 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 161 | # using the given strftime format. 162 | #html_last_updated_fmt = '%b %d, %Y' 163 | 164 | # If true, SmartyPants will be used to convert quotes and dashes to 165 | # typographically correct entities. 166 | #html_use_smartypants = True 167 | 168 | # Custom sidebar templates, maps document names to template names. 169 | #html_sidebars = {} 170 | 171 | # Additional templates that should be rendered to pages, maps page names to 172 | # template names. 173 | #html_additional_pages = {} 174 | 175 | # If false, no module index is generated. 176 | #html_domain_indices = True 177 | 178 | # If false, no index is generated. 179 | #html_use_index = True 180 | 181 | # If true, the index is split into individual pages for each letter. 182 | #html_split_index = False 183 | 184 | # If true, links to the reST sources are added to the pages. 185 | #html_show_sourcelink = True 186 | 187 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 188 | #html_show_sphinx = True 189 | 190 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 191 | #html_show_copyright = True 192 | 193 | # If true, an OpenSearch description file will be output, and all pages will 194 | # contain a tag referring to it. The value of this option must be the 195 | # base URL from which the finished HTML is served. 196 | #html_use_opensearch = '' 197 | 198 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 199 | #html_file_suffix = None 200 | 201 | # Output file base name for HTML help builder. 202 | htmlhelp_basename = 'pytrajectorydoc' 203 | 204 | 205 | # -- Options for LaTeX output --------------------------------------------- 206 | 207 | latex_elements = { 208 | # The paper size ('letterpaper' or 'a4paper'). 209 | #'papersize': 'letterpaper', 210 | 211 | # The font size ('10pt', '11pt' or '12pt'). 212 | #'pointsize': '10pt', 213 | 214 | # Additional stuff for the LaTeX preamble. 215 | 'preamble': '\usepackage{amssymb}', 216 | } 217 | 218 | # Grouping the document tree into LaTeX files. List of tuples 219 | # (source start file, target name, title, 220 | # author, documentclass [howto, manual, or own class]). 221 | latex_documents = [ 222 | ('index', 'pytrajectory.tex', u'pytrajectory Documentation', 223 | u'Andreas Kunze', 'manual'), 224 | ] 225 | 226 | # The name of an image file (relative to this directory) to place at the top of 227 | # the title page. 228 | #latex_logo = None 229 | 230 | # For "manual" documents, if this is true, then toplevel headings are parts, 231 | # not chapters. 232 | #latex_use_parts = False 233 | 234 | # If true, show page references after internal links. 235 | #latex_show_pagerefs = False 236 | 237 | # If true, show URL addresses after external links. 238 | #latex_show_urls = False 239 | 240 | # Documents to append as an appendix to all manuals. 241 | #latex_appendices = [] 242 | 243 | # If false, no module index is generated. 244 | #latex_domain_indices = True 245 | 246 | 247 | # -- Options for manual page output --------------------------------------- 248 | 249 | # One entry per manual page. List of tuples 250 | # (source start file, name, description, authors, manual section). 251 | man_pages = [ 252 | ('index', 'pytrajectory', u'pytrajectory Documentation', 253 | [u'Andreas Kunze'], 1) 254 | ] 255 | 256 | # If true, show URL addresses after external links. 257 | #man_show_urls = False 258 | 259 | 260 | # -- Options for Texinfo output ------------------------------------------- 261 | 262 | # Grouping the document tree into Texinfo files. List of tuples 263 | # (source start file, target name, title, author, 264 | # dir menu entry, description, category) 265 | texinfo_documents = [ 266 | ('index', 'pytrajectory', u'pytrajectory Documentation', 267 | u'Andreas Kunze', 'pytrajectory', 'One line description of project.', 268 | 'Miscellaneous'), 269 | ] 270 | 271 | # Documents to append as an appendix to all manuals. 272 | #texinfo_appendices = [] 273 | 274 | # If false, no module index is generated. 275 | #texinfo_domain_indices = True 276 | 277 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 278 | #texinfo_show_urls = 'footnote' 279 | 280 | # If true, do not generate a @detailmenu in the "Top" node's menu. 281 | #texinfo_no_detailmenu = False 282 | -------------------------------------------------------------------------------- /doc/source/guide/about.rst: -------------------------------------------------------------------------------- 1 | .. _about: 2 | 3 | About PyTrajectory 4 | ================== 5 | 6 | PyTrajectory is a Python library for trajectory generation for nonlinear control systems. 7 | It relies on solving a boundary value problem (bvp) via a collocation method. It is based 8 | on the scientific work of :ref:`Graichen et al. `, but does not depend on proprietary 9 | code like Matlabs *bvp4c*. 10 | 11 | PyTrajectory is developed at Dresden University of Technology at the 12 | `Institute for Control Theory `_, see also 13 | `other control related software `_. 14 | 15 | Based on the project thesis of Oliver Schnabel under the supervision of Carsten Knoll in February 2013 16 | it has been further developed by Andreas Kunze to increase its numeric performance. 17 | 18 | .. _contacts: 19 | 20 | Contacts 21 | -------- 22 | 23 | If you face any problems using PyTrajectory, feel free to contact us. 24 | 25 | * andreas.kunze mailbox.tu-dresden.de 26 | * carsten.knoll tu-dresden.de 27 | 28 | .. _license: 29 | 30 | Licence 31 | ------- 32 | 33 | .. literalinclude:: /../../LICENSE 34 | 35 | -------------------------------------------------------------------------------- /doc/source/guide/approach.rst: -------------------------------------------------------------------------------- 1 | .. _non-standard-approach: 2 | 3 | Non-Standard Approach 4 | ===================== 5 | 6 | There are implemented two different approaches for the polynomial parts 7 | of the splines in PyTrajectory. They differ in the choice of the nodes. 8 | Given an interval :math:`[x_k, x_{k+1}],\ k \in [0, \ldots, N-1]` the standard 9 | way in spline interpolation is to define the corresponding polynomial 10 | part using the left endpoint by 11 | 12 | .. math:: 13 | 14 | P_k(t) = c_{k,0} (t - x_k)^3 + c_{k,1}(t - x_k)^2 + c_{k,2}(t - x_k) + c_{k,3} 15 | 16 | However, in the thesis of O. Schnabel from which PyTrajectory emerged a different approach 17 | is used. He defined the polynomial parts using the right endpoint, i.e. 18 | 19 | .. math:: 20 | 21 | P_k(t) = c_{k,0} (t - x_{k+1})^3 + c_{k,1}(t - x_{k+1})^2 + c_{k,2}(t - x_{k+1}) + c_{k,3} 22 | 23 | This results in a different matrix to ensure the smoothness and boundary conditions 24 | of the splines: 25 | 26 | .. math:: 27 | :nowrap: 28 | 29 | \setcounter{MaxMatrixCols}{20} 30 | \setlength{\arraycolsep}{3pt} 31 | \newcommand\bigzero{\makebox(0,0){\text{\huge0}}} 32 | \begin{equation*} 33 | \textstyle 34 | \underbrace{\begin{bmatrix} 35 | 0 & 0 & 0 & 1 & h^3 & -h^2 & h & -1 \\ 36 | 0 & 0 & 1 & 0 & -3h^2 & 2h & -1 & 0 &&&& \bigzero \\ 37 | 0 & 2 & 0 & 0 & 6h & -2 & 0 & 0 \\ 38 | & & & & 0 & 0 & 0 & 1 & h^3 & -h^2 & h & -1 \\ 39 | & \bigzero & & & 0 & 0 & 1 & 0 & -3h^2 & 2h & -1 & 0 &&&&&& \bigzero \\ 40 | & & & & 0 & 2 & 0 & 0 & 6h & -2 & 0 & 0 \\ 41 | &&&&&&&&&&& \ddots \\ 42 | & & & & & & & & & & & & 0 & 0 & 0 & 1 & h^3 & -h^2 & h & -1 \\ 43 | & & & & & & \bigzero & & & & & & 0 & 0 & 1 & 0 & -3h^2 & 2h & -1 & 0 \\ 44 | & & & & & & & & & & & & 0 & 2 & 0 & 0 & 6h & -2 & 0 & 0 \\ 45 | & & & & & & & & & & & & & \\ 46 | -h^3 & h^2 & -h & 1 \\ 47 | 3h^2 & -2h & 1 & 0 &&&&&&&& \bigzero \\ 48 | -6h & 2 & 0 & 0 \\ 49 | & & & & & & & & & & & & & & & & 0 & 0 & 0 & 1 \\ 50 | & & & & & & \bigzero & & & & & & & & & & 0 & 0 & 1 & 0 \\ 51 | & & & & & & & & & & & & & & & & 0 & 2 & 0 & 0 \\ 52 | \end{bmatrix}}_{=: \boldsymbol{M}} 53 | \end{equation*} 54 | 55 | The reason for the two different approaches being implemented is that after implementing and switching to the standard approach 56 | some of the examples no longer converged to a solution. 57 | The examples that are effected by that are: 58 | 59 | - :ref:`con-double-pendulum` 60 | - :ref:`inv-3-bar-pend` 61 | -------------------------------------------------------------------------------- /doc/source/guide/examples/acrobot.rst: -------------------------------------------------------------------------------- 1 | .. _ex_acrobot: 2 | 3 | Acrobot 4 | ------- 5 | 6 | One further interesting example is that of the acrobot. The model can be 7 | regarded as a simplified gymnast hanging on a horizontal bar with both hands. 8 | The movements of the entire system is to be controlled only by movement of the hip. 9 | The body of the gymnast is represented by two rods which are jointed in the joint 10 | :math:`G_2`. The first rod is movably connected at joint :math:`G_1` with the inertial 11 | system, which corresponds to the encompassing of the stretching rod with the hands. 12 | 13 | For the model, two equal-length rods with a length :math:`l_1 = l_2 = l` are assumed 14 | with a homogeneous distribution of mass :math:`m_1 = m_2 = m` over the entire rod length. 15 | This does not correspond to the proportions of a man, also no restrictions were placed 16 | on the mobility of the hip joint. 17 | 18 | The following figure shows the schematic representation of the model. 19 | 20 | .. image:: /../pic/acrobot.png 21 | :scale: 80 22 | 23 | Using the previously assumed model parameters and the write abbreviations 24 | 25 | .. math:: 26 | :nowrap: 27 | 28 | \begin{eqnarray*} 29 | I & = & \frac{1}{3}m l^2 \\ 30 | d_{11} & = & \frac{m l^2}{4} + m(l^2 + \frac{l^2}{4} + l^2 \cos(\theta_2)) + 2I \\ 31 | h_1 & = & - \frac{m l^2}{2} \sin(\theta_2) (\dot{\theta}_2 (\dot{\theta}_2 + 2\dot{\theta}_1)) \\ 32 | d_{12} & = & m (\frac{l^2}{4} + \frac{l^2}{2} \cos(\theta_1)) + I \\ 33 | \varphi_1 & = & \frac{3}{2}m l g \cos(\theta_1) + \frac{1}{2}m l g \cos(\theta_1 + \theta_2) 34 | \end{eqnarray*} 35 | 36 | as well as the state vector :math:`x = [\theta_2, \dot{\theta}_2, \theta_1, \dot{\theta}_1]` one obtains 37 | the following state representation with the virtual input :math:`u = \ddot{\theta}_2` 38 | 39 | .. math:: 40 | :nowrap: 41 | 42 | \begin{eqnarray*} 43 | \dot{x}_1 & = & x_2 \\ 44 | \dot{x}_2 & = & u \\ 45 | \dot{x}_3 & = & x_4 \\ 46 | \dot{x}_4 & = & -d_{11}^{-1} (h_1 + \varphi_1 + d_{12}u) 47 | \end{eqnarray*} 48 | 49 | Now, the trajectory of the manipulated variable for an oscillation of the gymnast should be determined. 50 | The starting point of the exercise are the two downward hanging rods. These are to be transferred into another 51 | rest position in which the two bars show vertically upward within an operating time of :math:`T = 2 [s]`. 52 | At the beginning and end of the process, the input variable is to merge continuously into the rest 53 | position :math:`u(0) = u(T) = 0`. 54 | 55 | The initial and final states thus are 56 | 57 | .. math:: 58 | :nowrap: 59 | 60 | \begin{equation*} 61 | x(0) = \begin{bmatrix} 0 \\ 0 \\ \frac{3}{2} \pi \\ 0 \end{bmatrix} 62 | \rightarrow 63 | x(T) = \begin{bmatrix} 0 \\ 0 \\ \frac{1}{2} \pi \\ 0 \end{bmatrix} 64 | \end{equation*} 65 | 66 | .. only:: html 67 | 68 | .. image:: /../pic/acrobot.gif 69 | 70 | Source Code 71 | +++++++++++ 72 | 73 | .. literalinclude:: /../../examples/ex5_Acrobot.py 74 | :lines: 1-58 75 | 76 | 77 | -------------------------------------------------------------------------------- /doc/source/guide/examples/aircraft.rst: -------------------------------------------------------------------------------- 1 | .. _ex_aircraft: 2 | 3 | Aircraft 4 | -------- 5 | 6 | In this section we consider the model of a unmanned vertical take-off 7 | aircraft. The aircraft has two permanently mounted thrusters on the 8 | wings which can apply the thrust forces :math:`F_1` and :math:`F_2` 9 | independently of each other. The two engines are inclined by an angle 10 | :math:`\alpha` with respect to the aircraft-fixed axis :math:`\eta_2` 11 | and engage in the points :math:`P_1 = (l, h)` and :math:`P_2 = (-l,-h)`. 12 | The coordinates of the center of mass :math:`M` of the aircraft in the 13 | inertial system are denoted by :math:`z_1` and :math:`z_2`. At the same 14 | time, the point is the origin of the plane coordinate system. The 15 | aircraft axes are rotated by the angle :math:`\theta` with respect to 16 | the :math:`z_2`-axis. 17 | 18 | .. image:: /../pic/aircraft.png 19 | :scale: 80 20 | 21 | Through the establishment of the momentum balances for the model one 22 | obtains the equations 23 | 24 | .. math:: 25 | :nowrap: 26 | 27 | \begin{eqnarray*} 28 | m \ddot{z}_1 & = & - \sin(\theta)(F_1 + F_2)\cos(\alpha) + \cos(\theta)(F_1 - F_2)\sin(\alpha) \\ 29 | m \ddot{z}_2 & = & \cos(\theta)(F_1 + F_2)\sin(\alpha) + \sin(\theta)(F_1 - F_2)\cos(\alpha) - mg \\ 30 | J \ddot{\theta} & = & (F_1 - F_2)(l \cos(\alpha) + h \sin(\alpha)) 31 | \end{eqnarray*} 32 | 33 | With the state vector :math:`x = [z_1, \dot{z}_1, z_2, \dot{z}_2, \theta, \dot{\theta}]^T` 34 | and :math:`u = [u_1, u_2]^T = [F_1, F_2]^T` the state space 35 | representation of the system is as follows. 36 | 37 | .. math:: 38 | :nowrap: 39 | 40 | \begin{eqnarray*} 41 | \dot{x}_1 & = & x_2 \\ 42 | \dot{x}_2 & = & \frac{1}{m}(-\sin(x_5)(u_1 + u_2)\cos(\alpha) + \cos(x_5)(u_1 - u_2)\sin(\alpha)) \\ 43 | \dot{x}_3 & = & x_4 \\ 44 | \dot{x}_2 & = & \frac{1}{m}(\cos(x_5)(u_1 + u_2)\cos(\alpha) + \sin(x_5)(u_1 - u_2)\sin(\alpha)) - g \\ 45 | \dot{x}_5 & = & x_6 \\ 46 | \dot{x}_6 & = & \frac{1}{J}(l \cos(\alpha) + h \sin(\alpha)) 47 | \end{eqnarray*} 48 | 49 | For the aircraft, a trajectory should be planned that translates the 50 | horizontally aligned flying object from a rest position (hovering) along 51 | the :math:`z_1` and :math:`z_2` axis back into a hovering position. 52 | The hovering is to be realized on the boundary conditions of the input. 53 | Therefor the derivatives of the state variables should satisfy the 54 | following conditions. 55 | 56 | .. math:: 57 | :nowrap: 58 | 59 | $ \dot{z}_1 = \ddot{z}_1 = \dot{z}_2 = \ddot{z_2} = \dot{\theta} = \ddot{\theta} = 0 $ 60 | 61 | For the horizontal position applies :math:`\theta = 0`. These demands 62 | yield the boundary conditions for the inputs. 63 | 64 | .. math:: 65 | :nowrap: 66 | 67 | $ F_1(0) = F_1(T) = F_2(0) = F_2(T) = \frac{mg}{2 \cos(\alpha)} $ 68 | 69 | .. only:: html 70 | 71 | .. image:: /../pic/aircraft.gif 72 | 73 | Source Code 74 | +++++++++++ 75 | 76 | .. literalinclude:: /../../examples/ex3_Aircraft.py 77 | :lines: 1-59 78 | 79 | 80 | -------------------------------------------------------------------------------- /doc/source/guide/examples/con_double_integrator.rst: -------------------------------------------------------------------------------- 1 | .. _constrained_double_integrator: 2 | 3 | Constrained double integrator 4 | ----------------------------- 5 | 6 | This example is intended to present PyTrajectory's capabilities on handling system constraints. 7 | To do so, consider the double integrator which models the dynamics of a simple mass in an one-dimensional space, 8 | where a force effects the acceleration. The state space representation is given by the following dynamical system. 9 | 10 | .. math:: 11 | :nowrap: 12 | 13 | \begin{eqnarray*} 14 | \dot{x_1} = x_2 \\ 15 | \dot{x_2} = u_1 16 | \end{eqnarray*} 17 | 18 | A possibly wanted trajectory is the translation from :math:`x_1(t_0 = 0) = 0` to :math:`x_1(T) = 1` within 19 | :math:`T = 2[s]`. At the beginning and end the mass should stay at rest, that is :math:`x_2(0) = x_2(2) = 0`. 20 | 21 | Now, suppose we want the velocity to be bounded by :math:`x_{2,min} = 0.0 \leq x_2 \leq 0.65 = x_{2,max}`. 22 | To achieve this PyTrajectory needs a dictionary containing the index of the constrained variable in 23 | :math:`x = [x_1, x_2]` and a tuple with the corresponding constraints. So, normally this would look like :: 24 | 25 | >>> con = {1 : [0.0, 0.65]} 26 | 27 | But, due to how the approach for handling system constraints is implemented, this would throw an exception because 28 | the lower bound of the constraints :math:`x_{2,min}` is equal to :math:`x_2(0)` and has to be smaller. 29 | So instead we use the dictionary :: 30 | 31 | >>> con = {1 : [-0.1, 0.65]} 32 | 33 | .. only:: html 34 | 35 | .. image:: /../pic/con_double_integrator.gif 36 | 37 | Source Code 38 | +++++++++++ 39 | 40 | .. literalinclude:: /../../examples/ex6_ConstrainedDoubleIntegrator.py 41 | :lines: 1-29 42 | 43 | -------------------------------------------------------------------------------- /doc/source/guide/examples/con_double_pendulum.rst: -------------------------------------------------------------------------------- 1 | .. _con-double-pendulum: 2 | 3 | Constrained swing up of the inverted double pendulum 4 | ---------------------------------------------------- 5 | 6 | In this example we consider the inverted double pendulum. 7 | ... to be continued! 8 | 9 | .. only:: html 10 | 11 | .. image:: /../pic/con_double_pend_swing.gif 12 | 13 | Source Code 14 | +++++++++++ 15 | 16 | .. literalinclude:: /../../examples/ex8_ConstrainedDoublePendulum.py 17 | :lines: 1-204 18 | -------------------------------------------------------------------------------- /doc/source/guide/examples/con_inv_pendulum_swing.rst: -------------------------------------------------------------------------------- 1 | .. _constrained_inverted_pendulum: 2 | 3 | Constrained swing up of the inverted pundulum 4 | --------------------------------------------- 5 | 6 | Reconsider the example of the inverted pendulum in the :ref:`usage` section. 7 | 8 | This example is intended to show how PyTrajectory can handle constraints that affect 9 | some state variables. Assume we want to restrict the carts movement along the :math:`x`-axis 10 | to the interval :math:`[-0.8, 0.3]` that is :math:`\forall t \quad -0.8 \leq x_1(t) \leq 0.3` 11 | (remember: :math:`[x_1, x_2, x_3, x_4] = [x_w, \dot{x_w}, \varphi, \dot{\varphi}]`). 12 | Furthermore we want the velocity of the cart to be bounded by :math:`[-2.0, 2.0]`. 13 | 14 | To set these constraints PyTrajectory expects a dictionary containing the index of the constrained 15 | variables as keys and the box constraints as corresponding values. In our case this dictionary 16 | would look like :: 17 | 18 | >>> con = {0 : [-0.8, 0.3], 1 : [-2.0, 2.0]} 19 | 20 | (remember that Python starts indexing at :math:`0`). 21 | 22 | In order to get a solution we raise the translation time from :math:`T = 2[s]` to :math:`T = 3[s]`. 23 | Next, the only different thing to do is to pass the dictionary when instantiating the trajectory 24 | object. :: 25 | 26 | >>> T = Trajectory(f, a, b=3.0, xa, xb, uab, constraints=con) 27 | 28 | .. only:: html 29 | 30 | .. image:: /../pic/con_inv_pend_swing.gif 31 | 32 | Source Code 33 | +++++++++++ 34 | 35 | .. literalinclude:: /../../examples/ex7_ConstrainedInvertedPendulum.py 36 | :lines: 1-44 37 | 38 | -------------------------------------------------------------------------------- /doc/source/guide/examples/index.rst: -------------------------------------------------------------------------------- 1 | .. _examples: 2 | 3 | Examples 4 | ======== 5 | 6 | The following example systems from mechanics demonstrate the application 7 | of PyTrajectory. The derivation of the model equations is omittted here. 8 | 9 | The source code of the examples can be downloaded `here `_. 10 | In order to run them simply type:: 11 | 12 | $ python ex_.py 13 | 14 | The results of the examples latest simulation are saved in a pickle dump file by default. 15 | To prevent this add the *no-pickle* command line argument to the command above. 16 | 17 | If you want to plot the results and/or animate the example system add the *plot* and/or 18 | the *animate* argument to the command. 19 | 20 | So the command may look something like:: 21 | 22 | $ python ex0_InvertedPendulumSwingUp.py no-pickle plot animate 23 | 24 | For even more examples, which might not be part of the documentation, have a look at the 25 | `repository `_. 26 | 27 | 28 | .. toctree:: 29 | :maxdepth: 1 30 | 31 | inv_pendulum_trans 32 | inv_dual_pendulum_swing 33 | aircraft 34 | uact_manipulator 35 | acrobot 36 | con_double_integrator 37 | con_inv_pendulum_swing 38 | con_double_pendulum 39 | inv_3_bar_pend 40 | -------------------------------------------------------------------------------- /doc/source/guide/examples/inv_3_bar_pend.rst: -------------------------------------------------------------------------------- 1 | .. _inv-3-bar-pend: 2 | 3 | Swing up of a 3-bar pendulum 4 | ---------------------------- 5 | 6 | Now we consider a cart with 3 pendulums attached to it. 7 | 8 | To get a callable function for the vector field of this dynamical system 9 | we need to set up and solve its motion equations for the accelaration. 10 | 11 | Therefore, the function :py:func:`n_bar_pendulum` generates the mass matrix 12 | :math:`M` and right hand site :math:`B` of the motion equations :math:`M\ddot{x} = B` 13 | for a general :math:`n`\ -bar pendulum, which we use for the case :math:`n = 3`. 14 | 15 | The formulas this function uses are taken from the project report 16 | *'Simulation of the inverted pendulum'* written by `C. Wachinger `_, 17 | *M. Pock* and *P. Rentrop* at the *Mathematics Departement, Technical University Munich* 18 | in December 2004. 19 | 20 | .. only:: html 21 | 22 | .. image:: /../pic/inv_3_bar_pend.gif 23 | 24 | Source Code 25 | +++++++++++ 26 | 27 | .. literalinclude:: /../../examples/ex9_TriplePendulum.py 28 | :lines: 1-349 29 | 30 | -------------------------------------------------------------------------------- /doc/source/guide/examples/inv_dual_pendulum_swing.rst: -------------------------------------------------------------------------------- 1 | .. _ex_inv_dbl_pend: 2 | 3 | Swing up of the inverted dual pendulum 4 | -------------------------------------- 5 | 6 | In this example we add another pendulum to the cart in the system. 7 | 8 | .. image:: /../pic/inv_dual_pendulum.png 9 | :scale: 80 10 | 11 | The system has the state vector :math:`x = [x_1, \dot{x}_1, 12 | \varphi_1, \dot{\varphi}_1, \varphi_2, \dot{\varphi}_2]`. A partial 13 | linearization with :math:`y = x_1` yields the following system state 14 | representation where :math:`\tilde{u} = \ddot{y}`. 15 | 16 | .. math:: 17 | :nowrap: 18 | 19 | \begin{eqnarray*} 20 | \dot{x}_1 & = & x_2 \\ 21 | \dot{x}_2 & = & \tilde{u} \\ 22 | \dot{x}_3 & = & x_4 \\ 23 | \dot{x}_4 & = & \frac{1}{l_1}(g \sin(x_3) + \tilde{u} \cos(x_3)) \\ 24 | \dot{x}_5 & = & x_6 \\ 25 | \dot{x}_6 & = & \frac{1}{l_2}(g \sin(x_5) + \tilde{u} \cos(x_5)) 26 | \end{eqnarray*} 27 | 28 | Here a trajectory should be planned that transfers the system between 29 | the following two positions of rest. At the beginning both pendulums 30 | should be directed downwards (:math:`\varphi_1 = \varphi_2 = \pi`). 31 | After a operating time of :math:`T = 2 [s]` the cart should be at the 32 | same position again and the pendulums should be at rest with 33 | :math:`\varphi_1 = \varphi_2 = 0`. 34 | 35 | .. math:: 36 | :nowrap: 37 | 38 | \begin{equation*} 39 | x(0) = \begin{bmatrix} 0 \\ 0 \\ \pi \\ 0 \\ \pi \\ 0 \end{bmatrix} 40 | \rightarrow 41 | x(T) = \begin{bmatrix} 0 \\ 0 \\ 0 \\ 0 \\ 0 \\ 0 \end{bmatrix} 42 | \end{equation*} 43 | 44 | .. only:: html 45 | 46 | .. image:: /../pic/inv_dual_pend_swing.gif 47 | 48 | Source Code 49 | +++++++++++ 50 | 51 | .. literalinclude:: /../../examples/ex2_InvertedDualPendulumSwingUp.py 52 | :lines: 1-46 53 | 54 | -------------------------------------------------------------------------------- /doc/source/guide/examples/inv_pendulum_trans.rst: -------------------------------------------------------------------------------- 1 | .. _ex_inv_pend: 2 | 3 | Translation of the inverted pendulum 4 | ------------------------------------ 5 | 6 | An example often used in literature is the inverted pendulum. Here a 7 | force :math:`F` acts on a cart with mass :math:`M_w`. In addition the 8 | cart is connected by a massless rod with a pendulum mass :math:`m_p`. 9 | The mass of the pendulum is concentrated in :math:`P_2` and that of the 10 | cart in :math:`P_1`. The state vector of the system can be specified 11 | using the carts position :math:`x_w(t)` and the pendulum deflection 12 | :math:`\varphi(t)` and their derivatives. 13 | 14 | .. image:: /../pic/inv_pendulum.png 15 | :scale: 80 16 | 17 | With the *Lagrangian Formalism* the model has the following state 18 | representation where :math:`u_1 = F` and 19 | :math:`x = [x_1, x_2, x_3, x_4] = [x_w, \dot{x}_w, \varphi, \dot{\varphi}]` 20 | 21 | .. math:: 22 | :nowrap: 23 | 24 | \begin{eqnarray*} 25 | \dot{x}_1 & = & x_2 \\ 26 | \dot{x}_2 & = & \frac{m_p \sin(x_3)(-l x_4^2 + g \cos x_3)}{M_w l + m_p \sin^2(x_3)} + \frac{\cos(x_3)}{M_w l + m_p l \sin^2(x_3)} u_1 \\ 27 | \dot{x}_3 & = & x_4 \\ 28 | \dot{x}_4 & = & \frac{\sin(x_3)(-m_p l x_4^2 \cos(x_3) + g(M_w + m_p))}{M_w l + m_p \sin^2(x_3)} + \frac{\cos(x_3)}{M_w l + m_p l \sin^2(x_3)} u_1 29 | \end{eqnarray*} 30 | 31 | A possibly wanted trajectory is the translation of the cart along the 32 | x-axis (i.e. by :math:`0.5m`). In the beginning and end of the process 33 | the cart and pendulum should remain at rest and the pendulum should be 34 | aligned vertically upwards (:math:`\varphi = 0`). As a further condition 35 | :math:`u_1` should start and end steadily in the rest position 36 | (:math:`u_1(0) = u_1(T) = 0`). 37 | The operating time here is :math:`T = 1 [s]`. 38 | 39 | .. only:: html 40 | 41 | .. image:: /../pic/inv_pend_trans.gif 42 | 43 | Source Code 44 | +++++++++++ 45 | 46 | .. literalinclude:: /../../examples/ex1_InvertedPendulumTranslation.py 47 | :lines: 1-48 48 | 49 | -------------------------------------------------------------------------------- /doc/source/guide/examples/uact_manipulator.rst: -------------------------------------------------------------------------------- 1 | .. _ex_unact_mani: 2 | 3 | Underactuated Manipulator 4 | ------------------------- 5 | 6 | In this section, the model of an underactuated manipulator is treated. 7 | The system consists of two bars with the mass :math:`M_1` and 8 | :math:`M_2` which are connected to each other via the joint :math:`G_2`. 9 | The angle between them is designated by :math:`\theta_2`. The joint 10 | :math:`G_1` connects the first rod with the inertial system, the angle 11 | to the :math:`x`-axis is labeled :math:`\theta_1`. 12 | In the joint :math:`G_1` the actuating torque :math:`Q` is applied. The 13 | bars have the moments of inertia :math:`I_1` and :math:`I_2`. The 14 | distances between the centers of mass to the joints are :math:`r_1` and 15 | :math:`r_2`. 16 | 17 | .. image:: /../pic/uact_manipulator.png 18 | :scale: 80 19 | 20 | The modeling was taken from the thesis of Carsten Knoll 21 | (April, 2009) where in addition the inertia parameter :math:`\eta` was 22 | introduced. 23 | 24 | .. math:: 25 | :nowrap: 26 | 27 | \begin{equation*} 28 | \eta = \frac{m_2 l_1 r_2}{I_2 + m_2 r_2^2} 29 | \end{equation*} 30 | 31 | For the example shown here, strong inertia coupling was assumed with 32 | :math:`\eta = 0.9`. By partial linearization to the output :math:`y = 33 | \theta_1` one obtains the state representation with the states 34 | :math:`x = [\theta_1, \dot{\theta}_1, \theta_2, \dot{\theta}_2]^T` and 35 | the new input :math:`\tilde{u} = \ddot{\theta}_1`. 36 | 37 | .. math:: 38 | :nowrap: 39 | 40 | \begin{eqnarray*} 41 | \dot{x}_1 & = & x_2 \\ 42 | \dot{x}_2 & = & \tilde{u} \\ 43 | \dot{x}_3 & = & x_4 \\ 44 | \dot{x}_4 & = & -\eta x_2^2 \sin(x_3) - (1 + \eta \cos(x_3))\tilde{u} 45 | \end{eqnarray*} 46 | 47 | For the system, a trajectory is to be determined for the transfer 48 | between two equilibrium positions within an operating time of 49 | :math:`T = 1.8 [s]`. 50 | 51 | .. math:: 52 | :nowrap: 53 | 54 | \begin{equation*} 55 | x(0) = \begin{bmatrix} 0 \\ 0 \\ 0.4 \pi \\ 0 \end{bmatrix} 56 | \rightarrow 57 | x(T) = \begin{bmatrix} 0.2 \pi \\ 0 \\ 0.2 \pi \\ 0 \end{bmatrix} 58 | \end{equation*} 59 | 60 | The trajectory of the inputs should be without cracks in the transition 61 | to the equilibrium positions (:math:`\tilde{u}(0) = \tilde{u}(T) = 0`). 62 | 63 | .. only:: html 64 | 65 | .. image:: /../pic/uact_manipulator.gif 66 | 67 | Source Code 68 | +++++++++++ 69 | 70 | .. literalinclude:: /../../examples/ex4_UnderactuatedManipulator.py 71 | :lines: 1-50 72 | 73 | -------------------------------------------------------------------------------- /doc/source/guide/index.rst: -------------------------------------------------------------------------------- 1 | PyTrajectory User's Guide 2 | ************************* 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | about 8 | start 9 | background 10 | examples/index 11 | .. notes 12 | -------------------------------------------------------------------------------- /doc/source/guide/notes.rst: -------------------------------------------------------------------------------- 1 | .. _notes: 2 | 3 | Notes on implementation 4 | ======================= 5 | 6 | This section contains some explanations on the code which don't fit in 7 | the docstrings or the background section. 8 | 9 | .. _eval_G_DG: 10 | 11 | Evaluation of the equation system and its jacobian 12 | ************************************************** 13 | 14 | Both the equation system and its jacobian consist of linear and 15 | nonlinear parts. Therefor their evaluation is divided into these two 16 | steps. 17 | 18 | In the following we denote by :math:`x_i,\ u_i` the spline object we 19 | created for the :math:`i`-th system or input variable and the first 20 | derivatives of the system variables by :math:`d x_i`. 21 | 22 | ... to be continued! 23 | 24 | .. _makesteady: 25 | 26 | Smoothness and boundary conditions for the splines 27 | ************************************************** 28 | 29 | ... to be continued! 30 | 31 | 32 | Some explanations on the method parameters 33 | ****************************************** 34 | 35 | ... to be continued! 36 | -------------------------------------------------------------------------------- /doc/source/guide/start.rst: -------------------------------------------------------------------------------- 1 | Getting Started 2 | *************** 3 | 4 | This section provides an overview on what PyTrajectory is and how to use it. 5 | For a more detailed view please have a look at the :ref:`reference`. 6 | 7 | .. contents:: Contents 8 | :local: 9 | 10 | 11 | What is PyTrajectory? 12 | ===================== 13 | 14 | PyTrajectory is a Python library for the determination of the feed forward control 15 | to achieve a transition between desired states of a nonlinear control system. 16 | 17 | Planning and designing of trajectories represents an important task in 18 | the control of technological processes. Here the problem is attributed 19 | on a multi-dimensional boundary value problem with free parameters. 20 | In general this problem can not be solved analytically. It is therefore 21 | resorted to the method of collocation in order to obtain a numerical 22 | approximation. 23 | 24 | PyTrajectory allows a flexible implementation of various tasks and enables an easy 25 | implementation. It suffices to supply a function :math:`f(x,u)` that represents the 26 | vectorfield of a control system and to specify the desired boundary values. 27 | 28 | 29 | Installation 30 | ============ 31 | 32 | PyTrajectory has been developed and tested on Python 2.7 33 | 34 | If you have troubles installing PyTrajectory, please don't hesitate to 35 | :ref:`contact ` us. 36 | 37 | .. _dependencies: 38 | 39 | Dependencies 40 | ------------ 41 | 42 | Before you install PyTrajectory make sure you have the following 43 | dependencies installed on your system. 44 | 45 | * numpy 46 | * sympy 47 | * scipy 48 | * optional 49 | * matplotlib [visualisation] 50 | * ipython [debugging] 51 | 52 | PyPI 53 | ---- 54 | 55 | The easiest way of installing PyTrajectory would be :: 56 | 57 | $ pip install pytrajectory 58 | 59 | provided that you have the Python module `pip` installed on your system. 60 | 61 | .. _source: 62 | 63 | Source 64 | ------ 65 | 66 | To install PyTrajectory from the source files please download the latest release 67 | from `here `_. 68 | After the download is complete open the archive and change directory 69 | into the extracted folder. Then all you have to do is run the following command :: 70 | 71 | $ python setup.py install 72 | 73 | Please note that there are different versions of PyTrajectory available (development version 74 | in github repository [various branches], release versions at PyPI). 75 | Because the documentation is build automatically upon the source code, there are also different 76 | versions of the docs available. Please make sure that you always use matching versions of 77 | code and documentation. 78 | 79 | Windows 80 | ------- 81 | 82 | To install PyTrajectory on Windows machines please make sure you have already installed Python 83 | version 2.7 on your system. If not, please 84 | `download `_ 85 | the latest version and install it by double-clicking the installer file. 86 | 87 | To be able to run the Python interpreter from any directory we have to append the *PATH* 88 | environment variable. This can be done by right-clicking the machine icon (usually on your Desktop, 89 | called *My Computer*), choosing *Properties*, selecting *Advance* and hitting *Environment Variables*. 90 | Then select the *PATH* (or *Path*) variable, click *Edit* an append the following at the end of the line :: 91 | 92 | ;C:\Python27\;C:\Python27\Scripts\ 93 | 94 | If you can't find a variable called *PATH* you can create it by clicking *New*, naming it *PATH* 95 | and insert the line above without the first *`;`* as the value. 96 | 97 | Before going on, open a command line with the shortcut consisting of the *Windows-key* and the *R*-key. 98 | Run *cmd* and after the command line interface started type the following: :: 99 | 100 | C:\> pip --version 101 | 102 | If it prints the version number of *pip* you can skip the next two steps. 103 | Else, the next thing to do is to install a Python software package called *Setuptools* that extends packaging 104 | and installation facilities. To do so, download the Python script 105 | `ez_setup.py `_ 106 | and run it by typing :: 107 | 108 | C:>\path\to\file\python ez_setup.py 109 | 110 | To simplify the installation of new packages we install a software called *pip*. This is simply done 111 | by downloading the file 112 | `get_pip.py `_ 113 | and running :: 114 | 115 | C:>\path\to\file\python get_pip.py 116 | 117 | from the command line again. 118 | 119 | After that, (and after you have installed the :ref:`dependencies ` with a similar command 120 | like the next one) you can run :: 121 | 122 | C:>\pip install pytrajectory 123 | 124 | and pip should manage to install PyTrajectory. 125 | 126 | Again, if you have troubles installing PyTrajectory, please :ref:`contact ` us. 127 | 128 | .. note:: 129 | The information provided in this section follows the guide available 130 | `here `_. 131 | 132 | MAC OSX 133 | ------- 134 | 135 | To install PyTrajectory on machines running OSX you first have to make sure there is Python version 2.7 136 | installed on your system (should be with OSX >= 10.8). To check this, open a terminal and type :: 137 | 138 | $ python --version 139 | 140 | If this is not the case we have to install it (obviously). To do so we will use a package manager called 141 | *Homebrew* that allows an installation procedure similar to Linux environments. But before we do this pease 142 | check if you have `XCode `_ installed. 143 | 144 | Homebrew can be installed 145 | by opening a terminal and typing :: 146 | 147 | $ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 148 | 149 | Once Homebrew is installed we insert its directory at the top of the *PATH* environment variable by adding 150 | the following line at the bottom of your `~\.profile` file (you have to relogin for this to take effect) :: 151 | 152 | export PATH=/usr/local/bin:/usr/local/sbin:$PATH 153 | 154 | Now, installing Python version 2.7 is as easy as typing :: 155 | 156 | $ brew install python2 157 | 158 | into a terminal. Homebrew also will install packages called *Setuptools* and *pip* that manage the installation 159 | of additional Python packages. 160 | 161 | Now, before installing PyTrajectory please make sure to install its :ref:`dependencies ` via :: 162 | 163 | $ pip install sympy 164 | 165 | and similar commands for the others. After that you can install Pytrajectory by typing :: 166 | 167 | $ pip install pytrajectory 168 | 169 | or install it from the :ref:`source files `. 170 | 171 | Again, if you have troubles installing PyTrajectory, please :ref:`contact ` us. 172 | 173 | .. note:: 174 | The information provided in this section follows the guide available 175 | `here `_. 176 | 177 | .. _usage: 178 | 179 | Usage 180 | ===== 181 | 182 | In order to illustrate the usage of PyTrajectory we consider the following simple example. 183 | 184 | 185 | A pendulum mass :math:`m_p` is connected by a massless rod of length :math:`l` to a cart :math:`M_w` 186 | on which a force :math:`F` acts to accelerate it. 187 | 188 | .. image:: /../pic/inv_pendulum.png 189 | 190 | A possible task would be the transfer between two angular positions of the pendulum. 191 | In this case, the pendulum should hang at first down (:math:`\varphi = \pi`) and is 192 | to be turned upwards (:math:`\varphi = 0`). At the end of the process, the car should be at 193 | the same position and both the pendulum and the cart should be at rest. 194 | The (partial linearised) system is represented by the following differential equations, 195 | where :math:`[x_1, x_2, x_3, x_4] = [x_w, \dot{x_w}, \varphi, \dot{\varphi}]` and 196 | :math:`u = \ddot{x}_w` is our control variable: 197 | 198 | .. math:: 199 | :nowrap: 200 | 201 | \begin{eqnarray*} 202 | \dot{x_1} & = & x_2 \\ 203 | \dot{x_2} & = & u \\ 204 | \dot{x_3} & = & x_4 \\ 205 | \dot{x_4} & = & \frac{1}{l}(g\ sin(x_3) + u\ cos(x_3)) 206 | \end{eqnarray*} 207 | 208 | To solve this problem we first have to define a function that returns the vectorfield of 209 | the system above. Therefor it is important that you use SymPy functions if necessary, which is 210 | the case here with :math:`sin` and :math:`cos`. 211 | 212 | So in Python this would be :: 213 | 214 | >>> from sympy import sin, cos 215 | >>> 216 | >>> def f(x,u): 217 | ... x1, x2, x3, x4 = x # system variables 218 | ... u1, = u # input variable 219 | ... 220 | ... l = 0.5 # length of the pendulum 221 | ... g = 9.81 # gravitational acceleration 222 | ... 223 | ... # this is the vectorfield 224 | ... ff = [ x2, 225 | ... u1, 226 | ... x4, 227 | ... (1/l)*(g*sin(x3)+u1*cos(x3))] 228 | ... 229 | ... return ff 230 | ... 231 | >>> 232 | 233 | Wanted is now the course for :math:`u(t)`, which transforms the system with the following start 234 | and end states within :math:`T = 2 [s]`. 235 | 236 | .. math:: 237 | :nowrap: 238 | 239 | \begin{equation*} 240 | x(0) = \begin{bmatrix} 0 \\ 0 \\ \pi \\ 0 \end{bmatrix} 241 | \rightarrow 242 | x(T) = \begin{bmatrix} 0 \\ 0 \\ 0 \\ 0 \end{bmatrix} 243 | \end{equation*} 244 | 245 | so we have to specify the boundary values at the beginning :: 246 | 247 | >>> from numpy import pi 248 | >>> 249 | >>> a = 0.0 250 | >>> xa = [0.0, 0.0, pi, 0.0] 251 | 252 | and end :: 253 | 254 | >>> b = 2.0 255 | >>> xb = [0.0, 0.0, 0.0, 0.0] 256 | 257 | The boundary values for the input variable are 258 | 259 | >>> ua = [0.0] 260 | >>> ub = [0.0] 261 | 262 | because we want :math:`u(0) = u(T) = 0`. 263 | 264 | Now we import all we need from PyTrajectory :: 265 | 266 | >>> from pytrajectory import ControlSystem 267 | 268 | and pass our parameters. :: 269 | 270 | >>> S = ControlSystem(f, a, b, xa, xb, ua, ub) 271 | 272 | All we have to do now to solve our problem is :: 273 | 274 | >>> x, u = S.solve() 275 | 276 | After the iteration has finished `x(t)` and `u(t)` are returned as callable 277 | functions for the system and input variables, where t has to be in (a,b). 278 | 279 | In this example we get a solution that satisfies the default tolerance 280 | for the boundary values of :math:`10^{-2}` after the 7th iteration step 281 | with 320 spline parts. But PyTrajectory enables you to improve its 282 | performance by altering some of its method parameters. 283 | 284 | For example if we increase the factor for raising the spline parts (default: 2) :: 285 | 286 | >>> S.set_param('kx', 5) 287 | 288 | and don't take advantage of the system structure (integrator chains) :: 289 | 290 | >>> S.set_param('use_chains', False) 291 | 292 | we get a solution after 3 steps with 125 spline parts. 293 | 294 | There are more method parameters you can change to speed things up, i.e. the type of 295 | collocation points to use or the number of spline parts for the input variables. 296 | To do so, just type:: 297 | 298 | >>> S.set_param('', ) 299 | 300 | Please have a look at the :ref:`reference` for more information. 301 | 302 | .. _visualisation: 303 | 304 | Visualisation 305 | ============= 306 | 307 | Beyond the simple :meth:`plot` method (see: :ref:`reference`) 308 | PyTrajectory offers basic capabilities to animate the given system. 309 | This is done via the :class:`Animation` class from the :mod:`utilities` 310 | module. To explain this feature we take a look at the example above. 311 | 312 | When instanciated, the :class:`Animation` requires the calculated 313 | simulation results `T.sim` and a callable function that draws an image 314 | of the system according to given simulation data. 315 | 316 | First we import what we need by:: 317 | 318 | >>> import matplotlib as mpl 319 | >>> from pytrajectory.visualisation import Animation 320 | 321 | Then we define our function that takes simulation data `x` of a 322 | specific time and an instance `image` of `Animation.Image` which is just 323 | a container for the image. In the considered example `xt` is of the form 324 | 325 | .. math:: 326 | :nowrap: 327 | 328 | \begin{equation*} 329 | xt = [x_1, x_2, x_3, x_4] = [x_w, \dot{x}_w, \varphi, \dot{\varphi}] 330 | \end{equation*} 331 | 332 | and `image` is just a container for the drawn image. 333 | 334 | .. literalinclude:: /../../examples/ex0_InvertedPendulumSwingUp.py 335 | :lines: 55-105 336 | 337 | If we want to save the latest simulation result, maybe because the iteration 338 | took much time and we don't want to run it again every time, we can do this. 339 | 340 | .. literalinclude:: /../../examples/ex0_InvertedPendulumSwingUp.py 341 | :lines: 110 342 | 343 | Next, we create an instance of the :py:class:`Animation` class and 344 | pass our :py:func:`draw` function, the simulation data and some 345 | lists that specify what trajectory curves to plot along with the 346 | picture. 347 | 348 | If we would like to either plot the system state at the end time 349 | or want to animate the system we need to create an `Animation` object. 350 | To set the limits correctly we calculate the minimum and maximum 351 | value of the cart's movement along the `x`-axis. 352 | 353 | .. literalinclude:: /../../examples/ex0_InvertedPendulumSwingUp.py 354 | :lines: 118-124 355 | 356 | Finally, we can plot the system and/or start the animation. 357 | 358 | .. literalinclude:: /../../examples/ex0_InvertedPendulumSwingUp.py 359 | :lines: 126-132 360 | 361 | The animation can be saved either as animated .gif file or as a 362 | .mp4 video file. 363 | 364 | .. literalinclude:: /../../examples/ex0_InvertedPendulumSwingUp.py 365 | :lines: 135 366 | 367 | If saved as an animated .gif file you can view 368 | single frames using for example `gifview` (GNU/Linux) or the 369 | standard Preview app (OSX). 370 | 371 | .. only:: html 372 | 373 | .. image:: /../pic/inv_pend_swing.gif 374 | 375 | .. only:: latex 376 | 377 | .. image:: /../pic/inv_pend_swing.png 378 | 379 | -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | .. pytrajectory documentation master file, created by 2 | sphinx-quickstart2 on Sun Mar 23 14:31:12 2014. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to PyTrajectory's documentation! 7 | ======================================== 8 | 9 | .. Placeholder for the datetime string of latest commit 10 | 11 | This documentation is built automatically from the source code (commit: 2016-01-15 14:12:08) 12 | 13 | .. toctree:: 14 | :maxdepth: 2 15 | 16 | guide/index 17 | pytrajectory 18 | 19 | 20 | Indices and tables 21 | ================== 22 | 23 | * :ref:`genindex` 24 | * :ref:`modindex` 25 | * :ref:`search` 26 | -------------------------------------------------------------------------------- /doc/source/pytrajectory.rst: -------------------------------------------------------------------------------- 1 | .. _reference: 2 | 3 | PyTrajectory Modules Reference 4 | ****************************** 5 | 6 | PyTrajectory is a Python library for the determination of the feed 7 | forward control 8 | to achieve a transition between desired states of a nonlinear control 9 | system. 10 | 11 | .. contents:: Contents 12 | :local: 13 | 14 | 15 | :mod:`system` Module 16 | ==================== 17 | 18 | .. automodule:: pytrajectory.system 19 | :members: 20 | :member-order: bysource 21 | :undoc-members: 22 | :show-inheritance: 23 | 24 | :mod:`trajectories` Module 25 | ========================== 26 | 27 | .. automodule:: pytrajectory.trajectories 28 | :members: 29 | :member-order: bysource 30 | :undoc-members: 31 | :show-inheritance: 32 | 33 | :mod:`collocation` Module 34 | ========================= 35 | 36 | .. automodule:: pytrajectory.collocation 37 | :members: 38 | :member-order: 39 | :undoc-members: 40 | :show-inheritance: 41 | 42 | :mod:`splines` Module 43 | ===================== 44 | 45 | .. automodule:: pytrajectory.splines 46 | :members: 47 | :member-order: bysource 48 | :undoc-members: 49 | :show-inheritance: 50 | 51 | :mod:`solver` Module 52 | ==================== 53 | 54 | .. automodule:: pytrajectory.solver 55 | :members: 56 | :member-order: bysource 57 | :undoc-members: 58 | :show-inheritance: 59 | 60 | :mod:`simulation` Module 61 | ======================== 62 | 63 | .. automodule:: pytrajectory.simulation 64 | :members: 65 | :member-order: bysource 66 | :undoc-members: 67 | :show-inheritance: 68 | 69 | :mod:`auxiliary` Module 70 | ======================= 71 | 72 | .. automodule:: pytrajectory.auxiliary 73 | :members: 74 | :member-order: bysource 75 | :undoc-members: 76 | :show-inheritance: 77 | 78 | :mod:`visualisation` Module 79 | =========================== 80 | 81 | .. automodule:: pytrajectory.visualisation 82 | :members: 83 | :member-order: bysource 84 | :undoc-members: 85 | :show-inheritance: 86 | -------------------------------------------------------------------------------- /doc/source/requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | scipy 3 | sympy 4 | matplotlib 5 | ipython 6 | sphinxcontrib-napoleon 7 | -------------------------------------------------------------------------------- /examples/ex0_InvertedPendulumSwingUp.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This example of the inverted pendulum demonstrates the basic usage of 3 | PyTrajectory as well as its visualisation capabilities. 4 | ''' 5 | 6 | # import all we need for solving the problem 7 | from pytrajectory import ControlSystem 8 | import numpy as np 9 | from sympy import cos, sin 10 | from numpy import pi 11 | 12 | # the next imports are necessary for the visualisatoin of the system 13 | import sys 14 | import matplotlib as mpl 15 | from pytrajectory.visualisation import Animation 16 | 17 | # first, we define the function that returns the vectorfield 18 | def f(x,u): 19 | x1, x2, x3, x4 = x # system variables 20 | u1, = u # input variable 21 | 22 | l = 0.5 # length of the pendulum 23 | g = 9.81 # gravitational acceleration 24 | 25 | # this is the vectorfield 26 | ff = [ x2, 27 | u1, 28 | x4, 29 | (1/l)*(g*sin(x3)+u1*cos(x3))] 30 | 31 | return ff 32 | 33 | # then we specify all boundary conditions 34 | a = 0.0 35 | xa = [0.0, 0.0, pi, 0.0] 36 | 37 | b = 2.0 38 | xb = [0.0, 0.0, 0.0, 0.0] 39 | 40 | ua = [0.0] 41 | ub = [0.0] 42 | 43 | # now we create our Trajectory object and alter some method parameters via the keyword arguments 44 | S = ControlSystem(f, a, b, xa, xb, ua, ub, kx=5, use_chains=False) 45 | 46 | # time to run the iteration 47 | S.solve() 48 | 49 | 50 | # now that we (hopefully) have found a solution, 51 | # we can visualise our systems dynamic 52 | 53 | # therefore we define a function that draws an image of the system 54 | # according to the given simulation data 55 | def draw(xt, image): 56 | # to draw the image we just need the translation `x` of the 57 | # cart and the deflection angle `phi` of the pendulum. 58 | x = xt[0] 59 | phi = xt[2] 60 | 61 | # next we set some parameters 62 | car_width = 0.05 63 | car_heigth = 0.02 64 | 65 | rod_length = 0.5 66 | pendulum_size = 0.015 67 | 68 | # then we determine the current state of the system 69 | # according to the given simulation data 70 | x_car = x 71 | y_car = 0 72 | 73 | x_pendulum = -rod_length * sin(phi) + x_car 74 | y_pendulum = rod_length * cos(phi) 75 | 76 | # now we can build the image 77 | 78 | # the pendulum will be represented by a black circle with 79 | # center: (x_pendulum, y_pendulum) and radius `pendulum_size 80 | pendulum = mpl.patches.Circle(xy=(x_pendulum, y_pendulum), radius=pendulum_size, color='black') 81 | 82 | # the cart will be represented by a grey rectangle with 83 | # lower left: (x_car - 0.5 * car_width, y_car - car_heigth) 84 | # width: car_width 85 | # height: car_height 86 | car = mpl.patches.Rectangle((x_car-0.5*car_width, y_car-car_heigth), car_width, car_heigth, 87 | fill=True, facecolor='grey', linewidth=2.0) 88 | 89 | # the joint will also be a black circle with 90 | # center: (x_car, 0) 91 | # radius: 0.005 92 | joint = mpl.patches.Circle((x_car,0), 0.005, color='black') 93 | 94 | # and the pendulum rod will just by a line connecting the cart and the pendulum 95 | rod = mpl.lines.Line2D([x_car,x_pendulum], [y_car,y_pendulum], 96 | color='black', zorder=1, linewidth=2.0) 97 | 98 | # finally we add the patches and line to the image 99 | image.patches.append(pendulum) 100 | image.patches.append(car) 101 | image.patches.append(joint) 102 | image.lines.append(rod) 103 | 104 | # and return the image 105 | return image 106 | 107 | if not 'no-pickle' in sys.argv: 108 | # here we save the simulation results so we don't have to run 109 | # the iteration again in case the following fails 110 | S.save(fname='ex0_InvertedPendulumSwingUp.pcl') 111 | 112 | # now we can create an instance of the `Animation` class 113 | # with our draw function and the simulation results 114 | # 115 | # to plot the curves of some trajectories along with the picture 116 | # we also pass the appropriate lists as arguments (see documentation) 117 | if 'plot' in sys.argv or 'animate' in sys.argv: 118 | A = Animation(drawfnc=draw, simdata=S.sim_data, 119 | plotsys=[(0,'x'), (2,'phi')], plotinputs=[(0,'u')]) 120 | 121 | # as for now we have to explicitly set the limits of the figure 122 | # (may involves some trial and error) 123 | xmin = np.min(S.sim_data[1][:,0]); xmax = np.max(S.sim_data[1][:,0]) 124 | A.set_limits(xlim=(xmin - 0.5, xmax + 0.5), ylim=(-0.6,0.6)) 125 | 126 | if 'plot' in sys.argv: 127 | A.show(t=S.b) 128 | 129 | if 'animate' in sys.argv: 130 | # if everything is set, we can start the animation 131 | # (might take some while) 132 | A.animate() 133 | 134 | # then we can save the animation as a `mp4` video file or as an animated `gif` file 135 | A.save('ex0_InvertedPendulum.gif') 136 | 137 | -------------------------------------------------------------------------------- /examples/ex1_InvertedPendulumTranslation.py: -------------------------------------------------------------------------------- 1 | # translation of the inverted pendulum 2 | 3 | # import trajectory class and necessary dependencies 4 | from pytrajectory import ControlSystem 5 | from sympy import sin, cos 6 | import numpy as np 7 | 8 | # define the function that returns the vectorfield 9 | def f(x,u): 10 | x1, x2, x3, x4 = x # system state variables 11 | u1, = u # input variable 12 | 13 | l = 0.5 # length of the pendulum rod 14 | g = 9.81 # gravitational acceleration 15 | M = 1.0 # mass of the cart 16 | m = 0.1 # mass of the pendulum 17 | 18 | s = sin(x3) 19 | c = cos(x3) 20 | 21 | ff = np.array([ x2, 22 | m*s*(-l*x4**2+g*c)/(M+m*s**2)+1/(M+m*s**2)*u1, 23 | x4, 24 | s*(-m*l*x4**2*c+g*(M+m))/(M*l+m*l*s**2)+c/(M*l+l*m*s**2)*u1 25 | ]) 26 | return ff 27 | 28 | # boundary values at the start (a = 0.0 [s]) 29 | xa = [ 0.0, 30 | 0.0, 31 | 0.0, 32 | 0.0] 33 | 34 | # boundary values at the end (b = 2.0 [s]) 35 | xb = [ 1.0, 36 | 0.0, 37 | 0.0, 38 | 0.0] 39 | 40 | # create trajectory object 41 | S = ControlSystem(f, a=0.0, b=2.0, xa=xa, xb=xb) 42 | 43 | # change method parameter to increase performance 44 | S.set_param('use_chains', False) 45 | 46 | # run iteration 47 | S.solve() 48 | 49 | 50 | # the following code provides an animation of the system above 51 | # for a more detailed explanation have a look at the 'Visualisation' section in the documentation 52 | import sys 53 | import matplotlib as mpl 54 | from pytrajectory.visualisation import Animation 55 | 56 | def draw(xti, image): 57 | x = xti[0] 58 | phi = xti[2] 59 | 60 | L = 0.5 61 | 62 | car_width = 0.05 63 | car_heigth = 0.02 64 | pendel_size = 0.015 65 | 66 | x_car = x 67 | y_car = 0 68 | 69 | x_pendel =-L*sin(phi)+x_car 70 | y_pendel = L*cos(phi) 71 | 72 | # rod 73 | rod = mpl.lines.Line2D([x_car,x_pendel],[y_car,y_pendel],color='k',zorder=0,linewidth=2.0) 74 | 75 | # pendulum 76 | sphere = mpl.patches.Circle((x_pendel,y_pendel),pendel_size,color='k') 77 | 78 | # cart 79 | cart = mpl.patches.Rectangle((x_car-0.5*car_width,y_car-car_heigth),car_width,car_heigth, 80 | fill=True,facecolor='0.75',linewidth=2.0) 81 | 82 | # joint 83 | joint = mpl.patches.Circle((x_car,0),0.005,color='k') 84 | 85 | image.lines.append(rod) 86 | image.patches.append(sphere) 87 | image.patches.append(cart) 88 | image.patches.append(joint) 89 | 90 | return image 91 | 92 | if not 'no-pickle' in sys.argv: 93 | # here we save the simulation results so we don't have to run 94 | # the iteration again in case the following fails 95 | S.save(fname='ex1_InvertedPendulumTranslation.pcl') 96 | 97 | if 'plot' in sys.argv or 'animate' in sys.argv: 98 | A = Animation(drawfnc=draw, simdata=S.sim_data, 99 | plotsys=[(0,'x'), (2,'phi')], plotinputs=[(0,'u')]) 100 | 101 | xmin = np.min(S.sim_data[1][:,0]) 102 | xmax = np.max(S.sim_data[1][:,0]) 103 | A.set_limits(xlim=(xmin - 0.5,xmax + 0.5), ylim=(-0.3,0.8)) 104 | 105 | if 'plot' in sys.argv: 106 | A.show(t=S.b) 107 | 108 | if 'animate' in sys.argv: 109 | A.animate() 110 | A.save('ex1_InvertedPendulumTranslation.gif') 111 | -------------------------------------------------------------------------------- /examples/ex2_InvertedDualPendulumSwingUp.py: -------------------------------------------------------------------------------- 1 | # swing up of the inverted dual pendulum with partial linearization 2 | 3 | # import trajectory class and necessary dependencies 4 | from pytrajectory import ControlSystem 5 | from sympy import cos, sin 6 | import numpy as np 7 | 8 | # define the function that returns the vectorfield 9 | def f(x,u): 10 | x1, x2, x3, x4, x5, x6 = x # system variables 11 | u, = u # input variable 12 | 13 | # length of the pendulums 14 | l1 = 0.7 15 | l2 = 0.5 16 | 17 | g = 9.81 # gravitational acceleration 18 | 19 | ff = np.array([ x2, 20 | u, 21 | x4, 22 | (1/l1)*(g*sin(x3)+u*cos(x3)), 23 | x6, 24 | (1/l2)*(g*sin(x5)+u*cos(x5)) 25 | ]) 26 | 27 | return ff 28 | 29 | # system state boundary values for a = 0.0 [s] and b = 2.0 [s] 30 | xa = [0.0, 0.0, np.pi, 0.0, np.pi, 0.0] 31 | xb = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] 32 | 33 | # boundary values for the input 34 | ua = [0.0] 35 | ub = [0.0] 36 | 37 | # create trajectory object 38 | S = ControlSystem(f, a=0.0, b=2.0, xa=xa, xb=xb, ua=ua, ub=ub) 39 | 40 | # alter some method parameters to increase performance 41 | S.set_param('su', 10) 42 | S.set_param('eps', 8e-2) 43 | 44 | # run iteration 45 | S.solve() 46 | 47 | 48 | # the following code provides an animation of the system above 49 | # for a more detailed explanation have a look at the 'Visualisation' section in the documentation 50 | import sys 51 | import matplotlib as mpl 52 | from pytrajectory.visualisation import Animation 53 | 54 | def draw(xti, image): 55 | x, phi1, phi2 = xti[0], xti[2], xti[4] 56 | 57 | l1 = 0.7 58 | l2 = 0.5 59 | 60 | car_width = 0.05 61 | car_heigth = 0.02 62 | pendel_size = 0.015 63 | 64 | 65 | x_car = x 66 | y_car = 0 67 | 68 | x_pendel1 = -l1*sin(phi1)+x_car 69 | y_pendel1 = l1*cos(phi1) 70 | 71 | x_pendel2 = -l2*sin(phi2)+x_car 72 | y_pendel2 = l2*cos(phi2) 73 | 74 | 75 | # pendulums 76 | sphere1 = mpl.patches.Circle((x_pendel1,y_pendel1),pendel_size,color='k') 77 | sphere2 = mpl.patches.Circle((x_pendel2,y_pendel2),pendel_size,color='0.3') 78 | 79 | # car 80 | car = mpl.patches.Rectangle((x_car-0.5*car_width,y_car-car_heigth),car_width,car_heigth,fill=True,facecolor='0.75',linewidth=2.0) 81 | 82 | # joint 83 | joint = mpl.patches.Circle((x_car,0),0.005,color='k') 84 | 85 | # rods 86 | rod1 = mpl.lines.Line2D([x_car,x_pendel1],[y_car,y_pendel1],color='k',zorder=1,linewidth=2.0) 87 | rod2 = mpl.lines.Line2D([x_car,x_pendel2],[y_car,y_pendel2],color='0.3',zorder=1,linewidth=2.0) 88 | 89 | image.patches.append(sphere1) 90 | image.patches.append(sphere2) 91 | image.patches.append(car) 92 | image.patches.append(joint) 93 | image.lines.append(rod1) 94 | image.lines.append(rod2) 95 | 96 | return image 97 | 98 | if not 'no-pickle' in sys.argv: 99 | # here we save the simulation results so we don't have to run 100 | # the iteration again in case the following fails 101 | S.save(fname='ex2_InvertedDualPendulumSwingUp.pcl') 102 | 103 | if 'plot' in sys.argv or 'animate' in sys.argv: 104 | A = Animation(drawfnc=draw, simdata=S.sim_data, 105 | plotsys=[(0,'x'),(2,'phi1'),(4,'phi2')], plotinputs=[(0,'u')]) 106 | 107 | xmin = np.min(S.sim_data[1][:,0]) 108 | xmax = np.max(S.sim_data[1][:,0]) 109 | A.set_limits(xlim=(xmin - 1.0, xmax + 1.0), ylim=(-0.8,0.8)) 110 | 111 | if 'plot' in sys.argv: 112 | A.show(t=S.b) 113 | 114 | if 'animate' in sys.argv: 115 | A.animate() 116 | A.save('ex2_InvertedDualPendulumSwingUp.gif') 117 | -------------------------------------------------------------------------------- /examples/ex3_Aircraft.py: -------------------------------------------------------------------------------- 1 | # vertical take-off aircraft 2 | 3 | # import trajectory class and necessary dependencies 4 | from pytrajectory import ControlSystem 5 | from sympy import sin, cos 6 | import numpy as np 7 | from numpy import pi 8 | 9 | # define the function that returns the vectorfield 10 | def f(x,u): 11 | x1, x2, x3, x4, x5, x6 = x # system state variables 12 | u1, u2 = u # input variables 13 | 14 | # coordinates for the points in which the engines engage [m] 15 | l = 1.0 16 | h = 0.1 17 | 18 | g = 9.81 # graviational acceleration [m/s^2] 19 | M = 50.0 # mass of the aircraft [kg] 20 | J = 25.0 # moment of inertia about M [kg*m^2] 21 | 22 | alpha = 5/360.0*2*pi # deflection of the engines 23 | 24 | sa = sin(alpha) 25 | ca = cos(alpha) 26 | 27 | s = sin(x5) 28 | c = cos(x5) 29 | 30 | ff = np.array([ x2, 31 | -s/M*(u1+u2) + c/M*(u1-u2)*sa, 32 | x4, 33 | -g+c/M*(u1+u2) +s/M*(u1-u2)*sa , 34 | x6, 35 | 1/J*(u1-u2)*(l*ca+h*sa)]) 36 | 37 | return ff 38 | 39 | # system state boundary values for a = 0.0 [s] and b = 3.0 [s] 40 | xa = [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] 41 | xb = [10.0, 0.0, 5.0, 0.0, 0.0, 0.0] 42 | 43 | # boundary values for the inputs 44 | ua = [0.5*9.81*50.0/(cos(5/360.0*2*pi)), 0.5*9.81*50.0/(cos(5/360.0*2*pi))] 45 | ub = [0.5*9.81*50.0/(cos(5/360.0*2*pi)), 0.5*9.81*50.0/(cos(5/360.0*2*pi))] 46 | 47 | # create trajectory object 48 | S = ControlSystem(f, a=0.0, b=3.0, xa=xa, xb=xb, ua=ua, ub=ub) 49 | 50 | # don't take advantage of the system structure (integrator chains) 51 | # (this will result in a faster solution here) 52 | S.set_param('use_chains', False) 53 | 54 | # also alter some other method parameters to increase performance 55 | S.set_param('kx', 5) 56 | 57 | # run iteration 58 | S.solve() 59 | 60 | # the following code provides an animation of the system above 61 | # for a more detailed explanation have a look at the 'Visualisation' section in the documentation 62 | import sys 63 | import matplotlib as mpl 64 | from pytrajectory.visualisation import Animation 65 | 66 | def draw(xti, image): 67 | x, y, theta = xti[0], xti[2], xti[4] 68 | 69 | S = np.array( [ [0, 0.3], 70 | [-0.1, 0.1], 71 | [-0.7, 0], 72 | [-0.1, -0.05], 73 | [ 0, -0.1], 74 | [0.1, -0.05], 75 | [ 0.7, 0], 76 | [ 0.1, 0.1]]) 77 | 78 | xx = S[:,0].copy() 79 | yy = S[:,1].copy() 80 | 81 | S[:,0] = xx*cos(theta)-yy*sin(theta)+x 82 | S[:,1] = yy*cos(theta)+xx*sin(theta)+y 83 | 84 | aircraft = mpl.patches.Polygon(S, closed=True, facecolor='0.75') 85 | image.patches.append(aircraft) 86 | 87 | return image 88 | 89 | if not 'no-pickle' in sys.argv: 90 | # here we save the simulation results so we don't have to run 91 | # the iteration again in case the following fails 92 | S.save(fname='ex3_Aircraft.pcl') 93 | 94 | if 'plot' in sys.argv or 'animate' in sys.argv: 95 | A = Animation(drawfnc=draw, simdata=S.sim_data, 96 | plotsys=[(4,'theta')], plotinputs=[(0,'F1'),(1,'F2')]) 97 | A.set_limits(xlim=(-1,11), ylim=(-1,7)) 98 | 99 | if 'plot' in sys.argv: 100 | A.show(t=S.b) 101 | 102 | if 'animate' in sys.argv: 103 | A.animate() 104 | A.save('ex3_Aircraft.gif') 105 | -------------------------------------------------------------------------------- /examples/ex4_UnderactuatedManipulator.py: -------------------------------------------------------------------------------- 1 | # underactuated manipulator 2 | 3 | # import trajectory class and necessary dependencies 4 | from pytrajectory import ControlSystem 5 | import numpy as np 6 | from sympy import cos, sin 7 | 8 | # define the function that returns the vectorfield 9 | def f(x,u): 10 | x1, x2, x3, x4 = x # state variables 11 | u1, = u # input variable 12 | 13 | e = 0.9 # inertia coupling 14 | 15 | s = sin(x3) 16 | c = cos(x3) 17 | 18 | ff = np.array([ x2, 19 | u1, 20 | x4, 21 | -e*x2**2*s-(1+e*c)*u1 22 | ]) 23 | 24 | return ff 25 | 26 | # system state boundary values for a = 0.0 [s] and b = 1.8 [s] 27 | xa = [ 0.0, 28 | 0.0, 29 | 0.4*np.pi, 30 | 0.0] 31 | 32 | xb = [ 0.2*np.pi, 33 | 0.0, 34 | 0.2*np.pi, 35 | 0.0] 36 | 37 | # boundary values for the inputs 38 | ua = [0.0] 39 | ub = [0.0] 40 | 41 | # create trajectory object 42 | S = ControlSystem(f, a=0.0, b=1.8, xa=xa, xb=xb, ua=ua, ub=ub) 43 | 44 | # also alter some method parameters to increase performance 45 | S.set_param('su', 20) 46 | S.set_param('kx', 3) 47 | 48 | # run iteration 49 | S.solve() 50 | 51 | 52 | # the following code provides an animation of the system above 53 | # for a more detailed explanation have a look at the 'Visualisation' section in the documentation 54 | import sys 55 | import matplotlib as mpl 56 | from pytrajectory.visualisation import Animation 57 | 58 | def draw(xti, image): 59 | phi1, phi2 = xti[0], xti[2] 60 | 61 | L =0.4 62 | 63 | x1 = L*cos(phi1) 64 | y1 = L*sin(phi1) 65 | 66 | x2 = x1+L*cos(phi2+phi1) 67 | y2 = y1+L*sin(phi2+phi1) 68 | 69 | # rods 70 | rod1 = mpl.lines.Line2D([0,x1],[0,y1],color='k',zorder=0,linewidth=2.0) 71 | rod2 = mpl.lines.Line2D([x1,x2],[y1,y2],color='k',zorder=0,linewidth=2.0) 72 | 73 | # pendulums 74 | sphere1 = mpl.patches.Circle((x1,y1),0.01,color='k') 75 | sphere2 = mpl.patches.Circle((0,0),0.01,color='k') 76 | 77 | image.lines.append(rod1) 78 | image.lines.append(rod2) 79 | image.patches.append(sphere1) 80 | image.patches.append(sphere2) 81 | 82 | return image 83 | 84 | if not 'no-pickle' in sys.argv: 85 | # here we save the simulation results so we don't have to run 86 | # the iteration again in case the following fails 87 | S.save(fname='ex4_UnderactuatedManipulator.pcl') 88 | 89 | if 'plot' in sys.argv or 'animate' in sys.argv: 90 | A = Animation(drawfnc=draw, simdata=S.sim_data, 91 | plotsys=[(0,'phi1'), (2,'phi2')], plotinputs=[(0,'u')]) 92 | A.set_limits(xlim= (-0.1,0.6), ylim=(-0.4,0.65)) 93 | 94 | if 'plot' in sys.argv: 95 | A.show(t=S.b) 96 | 97 | if 'animate' in sys.argv: 98 | A.animate() 99 | A.save('ex4_UnderactuatedManipulator.gif') 100 | -------------------------------------------------------------------------------- /examples/ex5_Acrobot.py: -------------------------------------------------------------------------------- 1 | # acrobot 2 | 3 | # import trajectory class and necessary dependencies 4 | from pytrajectory import ControlSystem 5 | import numpy as np 6 | from sympy import cos, sin 7 | 8 | # define the function that returns the vectorfield 9 | def f(x,u): 10 | x1, x2, x3, x4 = x 11 | u1, = u 12 | 13 | m = 1.0 # masses of the rods [m1 = m2 = m] 14 | l = 0.5 # lengths of the rods [l1 = l2 = l] 15 | 16 | I = 1/3.0*m*l**2 # moments of inertia [I1 = I2 = I] 17 | g = 9.81 # gravitational acceleration 18 | 19 | lc = l/2.0 20 | 21 | d11 = m*lc**2+m*(l**2+lc**2+2*l*lc*cos(x1))+2*I 22 | h1 = -m*l*lc*sin(x1)*(x2*(x2+2*x4)) 23 | d12 = m*(lc**2+l*lc*cos(x1))+I 24 | phi1 = (m*lc+m*l)*g*cos(x3)+m*lc*g*cos(x1+x3) 25 | 26 | ff = np.array([ x2, 27 | u1, 28 | x4, 29 | -1/d11*(h1+phi1+d12*u1) 30 | ]) 31 | 32 | return ff 33 | 34 | 35 | # system state boundary values for a = 0.0 [s] and b = 2.0 [s] 36 | xa = [ 0.0, 37 | 0.0, 38 | 3/2.0*np.pi, 39 | 0.0] 40 | 41 | xb = [ 0.0, 42 | 0.0, 43 | 1/2.0*np.pi, 44 | 0.0] 45 | 46 | # boundary values for the inputs 47 | ua = [0.0] 48 | ub = [0.0] 49 | 50 | # create trajectory object 51 | S = ControlSystem(f, a=0.0, b=2.0, xa=xa, xb=xb, ua=ua, ub=ub) 52 | 53 | # alter some method parameters to increase performance 54 | S.set_param('su', 10) 55 | 56 | # run iteration 57 | S.solve() 58 | 59 | 60 | # the following code provides an animation of the system above 61 | # for a more detailed explanation have a look at the 'Visualisation' section in the documentation 62 | import sys 63 | import matplotlib as mpl 64 | from pytrajectory.visualisation import Animation 65 | 66 | def draw(xti, image): 67 | phi1, phi2 = xti[0], xti[2] 68 | 69 | L=0.5 70 | 71 | x1 = L*cos(phi2) 72 | y1 = L*sin(phi2) 73 | 74 | x2 = x1+L*cos(phi2+phi1) 75 | y2 = y1+L*sin(phi2+phi1) 76 | 77 | # rods 78 | rod1 = mpl.lines.Line2D([0,x1],[0,y1],color='k',zorder=0,linewidth=2.0) 79 | rod2 = mpl.lines.Line2D([x1,x2],[y1,y2],color='0.3',zorder=0,linewidth=2.0) 80 | 81 | # pendulums 82 | sphere1 = mpl.patches.Circle((x1,y1),0.01,color='k') 83 | sphere2 = mpl.patches.Circle((0,0),0.01,color='k') 84 | 85 | image.lines.append(rod1) 86 | image.lines.append(rod2) 87 | image.patches.append(sphere1) 88 | image.patches.append(sphere2) 89 | 90 | return image 91 | 92 | if not 'no-pickle' in sys.argv: 93 | # here we save the simulation results so we don't have to run 94 | # the iteration again in case the following fails 95 | S.save(fname='ex5_Acrobot.pcl') 96 | 97 | if 'plot' in sys.argv or 'animate' in sys.argv: 98 | A = Animation(drawfnc=draw, simdata=S.sim_data, 99 | plotsys=[(0,'phi1'),(2,'phi2')], plotinputs=[(0,'u')]) 100 | A.set_limits(xlim=(-1.1,1.1), ylim=(-1.1,1.1)) 101 | 102 | if 'plot' in sys.argv: 103 | A.show(t=S.b) 104 | 105 | if 'animate' in sys.argv: 106 | A.animate() 107 | A.save('ex5_Acrobot.gif') 108 | -------------------------------------------------------------------------------- /examples/ex6_ConstrainedDoubleIntegrator.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This example of the double integrator demonstrates how to pass constraints to PyTrajectory. 3 | ''' 4 | # imports 5 | from pytrajectory import ControlSystem 6 | import numpy as np 7 | 8 | # define the vectorfield 9 | def f(x,u): 10 | x1, x2 = x 11 | u1, = u 12 | 13 | ff = [x2, 14 | u1] 15 | 16 | return ff 17 | 18 | # system state boundary values for a = 0.0 [s] and b = 2.0 [s] 19 | xa = [0.0, 0.0] 20 | xb = [1.0, 0.0] 21 | 22 | # constraints dictionary 23 | con = {1 : [-0.1, 0.65]} 24 | 25 | # create the trajectory object 26 | S = ControlSystem(f, a=0.0, b=2.0, xa=xa, xb=xb, constraints=con, use_chains=False) 27 | 28 | # start 29 | x, u = S.solve() 30 | 31 | 32 | # the following code provides an animation of the system above 33 | # for a more detailed explanation have a look at the 'Visualisation' section in the documentation 34 | import sys 35 | import matplotlib as mpl 36 | from pytrajectory.visualisation import Animation 37 | 38 | def draw(xt, image): 39 | x = xt[0] 40 | 41 | car_width = 0.05 42 | car_heigth = 0.02 43 | 44 | x_car = x 45 | y_car = 0 46 | 47 | car = mpl.patches.Rectangle((x_car-0.5*car_width, y_car-car_heigth), car_width, car_heigth, 48 | fill=True, facecolor='grey', linewidth=2.0) 49 | 50 | image.patches.append(car) 51 | 52 | return image 53 | 54 | if not 'no-pickle' in sys.argv: 55 | # here we save the simulation results so we don't have to run 56 | # the iteration again in case the following fails 57 | S.save(fname='ex6_ConstrainedDoubleIntegrator.pcl') 58 | 59 | if 'plot' in sys.argv or 'animate' in sys.argv: 60 | A = Animation(drawfnc=draw, simdata=S.sim_data, 61 | plotsys=[(0,'x'), (1,'dx')], 62 | plotinputs=[(0,'u')]) 63 | xmin = np.min(S.sim_data[1][:,0]) 64 | xmax = np.max(S.sim_data[1][:,0]) 65 | A.set_limits(xlim=(xmin - 0.1, xmax + 0.1), ylim=(-0.1,0.1)) 66 | 67 | if 'plot' in sys.argv: 68 | A.show(t=S.b) 69 | 70 | if 'animate' in sys.argv: 71 | A.animate() 72 | A.save('ex6_ConstrainedDoubleIntegrator.gif') 73 | -------------------------------------------------------------------------------- /examples/ex7_ConstrainedInvertedPendulum.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This example of the inverted pendulum demonstrates how to handle possible state constraints. 3 | ''' 4 | 5 | # import all we need for solving the problem 6 | from pytrajectory import ControlSystem 7 | import numpy as np 8 | from sympy import cos, sin 9 | 10 | # first, we define the function that returns the vectorfield 11 | def f(x,u): 12 | x1, x2, x3, x4 = x # system variables 13 | u1, = u # input variable 14 | 15 | l = 0.5 # length of the pendulum 16 | g = 9.81 # gravitational acceleration 17 | 18 | # this is the vectorfield 19 | ff = [ x2, 20 | u1, 21 | x4, 22 | (1/l)*(g*sin(x3)+u1*cos(x3))] 23 | 24 | return ff 25 | 26 | # then we specify all boundary conditions 27 | a = 0.0 28 | xa = [0.0, 0.0, np.pi, 0.0] 29 | 30 | b = 3.0 31 | xb = [0.0, 0.0, 0.0, 0.0] 32 | 33 | ua = [0.0] 34 | ub = [0.0] 35 | 36 | # next, this is the dictionary containing the constraints 37 | con = { 0 : [-0.8, 0.3], 38 | 1 : [-2.0, 2.0] } 39 | 40 | # now we create our Trajectory object and alter some method parameters via the keyword arguments 41 | S = ControlSystem(f, a, b, xa, xb, ua, ub, constraints=con, kx=5, use_chains=False) 42 | 43 | # time to run the iteration 44 | S.solve() 45 | 46 | 47 | # the following code provides an animation of the system above 48 | # for a more detailed explanation have a look at the 'Visualisation' section in the documentation 49 | import sys 50 | import matplotlib as mpl 51 | from pytrajectory.visualisation import Animation 52 | 53 | def draw(xt, image): 54 | x = xt[0] 55 | phi = xt[2] 56 | 57 | car_width = 0.05 58 | car_heigth = 0.02 59 | 60 | rod_length = 0.5 61 | pendulum_size = 0.015 62 | 63 | x_car = x 64 | y_car = 0 65 | 66 | x_pendulum = -rod_length * sin(phi) + x_car 67 | y_pendulum = rod_length * cos(phi) 68 | 69 | pendulum = mpl.patches.Circle(xy=(x_pendulum, y_pendulum), radius=pendulum_size, color='black') 70 | car = mpl.patches.Rectangle((x_car-0.5*car_width, y_car-car_heigth), car_width, car_heigth, 71 | fill=True, facecolor='grey', linewidth=2.0) 72 | joint = mpl.patches.Circle((x_car,0), 0.005, color='black') 73 | rod = mpl.lines.Line2D([x_car,x_pendulum], [y_car,y_pendulum], 74 | color='black', zorder=1, linewidth=2.0) 75 | 76 | image.patches.append(pendulum) 77 | image.patches.append(car) 78 | image.patches.append(joint) 79 | image.lines.append(rod) 80 | 81 | return image 82 | 83 | if not 'no-pickle' in sys.argv: 84 | # here we save the simulation results so we don't have to run 85 | # the iteration again in case the following fails 86 | S.save(fname='ex7_ConstrainedInvertedPendulum.pcl') 87 | 88 | if 'plot' in sys.argv or 'animate' in sys.argv: 89 | A = Animation(drawfnc=draw, simdata=S.sim_data, 90 | plotsys=[(0,'x'), (1,'dx')], 91 | plotinputs=[(0,'u1')]) 92 | xmin = np.min(S.sim_data[1][:,0]) 93 | xmax = np.max(S.sim_data[1][:,0]) 94 | A.set_limits(xlim=(xmin - 0.5, xmax + 0.5), ylim=(-0.6,0.6)) 95 | 96 | if 'plot' in sys.argv: 97 | A.show(t=S.b) 98 | 99 | if 'animate' in sys.argv: 100 | A.animate() 101 | A.save('ex7_ConstrainedInvertedPendulum.gif') 102 | 103 | -------------------------------------------------------------------------------- /examples/ex8_ConstrainedDoublePendulum.py: -------------------------------------------------------------------------------- 1 | # constrained double pendulum 2 | 3 | # import all we need for solving the problem 4 | from pytrajectory import ControlSystem 5 | import numpy as np 6 | import sympy as sp 7 | from sympy import cos, sin, Matrix 8 | from numpy import pi 9 | 10 | 11 | # to define a callable function that returns the vectorfield 12 | # we first solve the motion equations of form Mx = B 13 | 14 | def solve_motion_equations(M, B, state_vars=[], input_vars=[], parameters_values=dict()): 15 | ''' 16 | Solves the motion equations given by the mass matrix and right hand side 17 | to define a callable function for the vector field of the respective 18 | control system. 19 | 20 | Parameters 21 | ---------- 22 | 23 | M : sympy.Matrix 24 | A sympy.Matrix containing sympy expressions and symbols that represents 25 | the mass matrix of the control system. 26 | 27 | B : sympy.Matrix 28 | A sympy.Matrix containing sympy expressions and symbols that represents 29 | the right hand site of the motion equations. 30 | 31 | state_vars : list 32 | A list with sympy.Symbols's for each state variable. 33 | 34 | input_vars : list 35 | A list with sympy.Symbols's for each input variable. 36 | 37 | parameter_values : dict 38 | A dictionary with a key:value pair for each system parameter. 39 | 40 | Returns 41 | ------- 42 | 43 | callable 44 | ''' 45 | 46 | M_shape = M.shape 47 | B_shape = B.shape 48 | assert(M_shape[0] == B_shape[0]) 49 | 50 | # at first we create a buffer for the string that we complete and execute 51 | # to dynamically define a function and return it 52 | fnc_str_buffer =''' 53 | def f(x, u): 54 | # System variables 55 | %s # x_str 56 | %s # u_str 57 | 58 | # Parameters 59 | %s # par_str 60 | 61 | # Sympy Common Expressions 62 | %s # cse_str 63 | 64 | # Vectorfield 65 | %s # ff_str 66 | 67 | return ff 68 | ''' 69 | 70 | ################################# 71 | # handle system state variables # 72 | ################################# 73 | # --> leads to x_str which shows how to unpack the state variables 74 | x_str = '' 75 | for var in state_vars: 76 | x_str += '%s, '%str(var) 77 | 78 | # as a last we remove the trailing '; ' to avoid syntax erros 79 | x_str = x_str + '= x' 80 | 81 | ########################## 82 | # handle input variables # 83 | ########################## 84 | # --> leads to u_str which will show how to unpack the inputs of the control system 85 | u_str = '' 86 | for var in input_vars: 87 | u_str += '%s, '%str(var) 88 | 89 | # after we remove the trailing '; ' to avoid syntax errors x_str will look like: 90 | # 'u1, u2, ... , um = u' 91 | u_str = u_str + '= u' 92 | 93 | ############################ 94 | # handle system parameters # 95 | ############################ 96 | # --> leads to par_str 97 | par_str = '' 98 | for k, v in parameters_values.items(): 99 | # 'k' is the name of a system parameter such as mass or gravitational acceleration 100 | # 'v' is its value in SI units 101 | par_str += '%s = %s; '%(str(k), str(v)) 102 | 103 | # as a last we remove the trailing '; ' from par_str to avoid syntax errors 104 | par_str = par_str[:-2] 105 | 106 | # now solve the motion equations w.r.t. the accelerations 107 | sol = M.solve(B) 108 | 109 | # use SymPy's Common Subexpression Elimination 110 | cse_list, cse_res = sp.cse(sol, symbols=sp.numbered_symbols('q')) 111 | 112 | ################################ 113 | # handle common subexpressions # 114 | ################################ 115 | # --> leads to cse_str 116 | cse_str = '' 117 | #cse_list = [(str(l), str(r)) for l, r in cse_list] 118 | for cse_pair in cse_list: 119 | cse_str += '%s = %s; '%(str(cse_pair[0]), str(cse_pair[1])) 120 | 121 | # add result of cse 122 | for i in xrange(M_shape[0]): 123 | cse_str += 'q%d_dd = %s; '%(i, str(cse_res[0][i])) 124 | 125 | cse_str = cse_str[:-2] 126 | 127 | ###################### 128 | # create vectorfield # 129 | ###################### 130 | # --> leads to ff_str 131 | ff_str = 'ff = [' 132 | 133 | for i in xrange(M_shape[0]): 134 | ff_str += '%s, '%str(state_vars[2*i+1]) 135 | ff_str += 'q%s_dd, '%(i) 136 | 137 | # remove trailing ',' and add closing brackets 138 | ff_str = ff_str[:-2] + ']' 139 | 140 | ############################ 141 | # Create callable function # 142 | ############################ 143 | # now we can replace all placeholders in the function string buffer 144 | fnc_str = fnc_str_buffer%(x_str, u_str, par_str, cse_str, ff_str) 145 | # and finally execute it which will create a python function 'f' 146 | exec(fnc_str) 147 | 148 | # now we have defined a callable function that can be used within PyTrajectory 149 | return f 150 | 151 | 152 | 153 | 154 | # system and input variables 155 | state_vars = sp.symbols('x, dx, phi1, dphi1, phi2, dphi2') 156 | input_vars = sp.symbols('F,') 157 | x, dx, phi1, dphi1, phi2, dphi2 = state_vars 158 | F, = input_vars 159 | 160 | # parameters 161 | l1 = 0.25 # 1/2 * length of the pendulum 1 162 | l2 = 0.25 # 1/2 * length of the pendulum 163 | m1 = 0.1 # mass of the pendulum 1 164 | m2 = 0.1 # mass of the pendulum 2 165 | m = 1.0 # mass of the car 166 | g = 9.81 # gravitational acceleration 167 | I1 = 4.0/3.0 * m1 * l1**2 # inertia 1 168 | I2 = 4.0/3.0 * m2 * l2**2 # inertia 2 169 | 170 | param_values = {'l1':l1, 'l2':l2, 'm1':m1, 'm2':m2, 'm':m, 'g':g, 'I1':I1, 'I2':I2} 171 | 172 | # mass matrix 173 | M = Matrix([[ m+m1+m2, (m1+2*m2)*l1*cos(phi1), m2*l2*cos(phi2)], 174 | [(m1+2*m2)*l1*cos(phi1), I1+(m1+4*m2)*l1**2, 2*m2*l1*l2*cos(phi2-phi1)], 175 | [ m2*l2*cos(phi2), 2*m2*l1*l2*cos(phi2-phi1), I2+m2*l2**2]]) 176 | 177 | # and right hand site 178 | B = Matrix([[ F + (m1+2*m2)*l1*sin(phi1)*dphi1**2 + m2*l2*sin(phi2)*dphi2**2 ], 179 | [ (m1+2*m2)*g*l1*sin(phi1) + 2*m2*l1*l2*sin(phi2-phi1)*dphi2**2 ], 180 | [ m2*g*l2*sin(phi2) + 2*m2*l1*l2*sin(phi1-phi2)*dphi1**2 ]]) 181 | 182 | f = solve_motion_equations(M, B, state_vars, input_vars) 183 | 184 | # then we specify all boundary conditions 185 | a = 0.0 186 | xa = [0.0, 0.0, pi, 0.0, pi, 0.0] 187 | 188 | b = 4.0 189 | xb = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] 190 | 191 | ua = [0.0] 192 | ub = [0.0] 193 | 194 | # here we specify the constraints for the velocity of the car 195 | con = {0 : [-1.0, 1.0], 196 | 1 : [-2.0, 2.0]} 197 | 198 | # now we create our Trajectory object and alter some method parameters via the keyword arguments 199 | S = ControlSystem(f, a, b, xa, xb, ua, ub, constraints=con, 200 | eps=2e-1, su=20, kx=2, use_chains=False, 201 | use_std_approach=False) 202 | 203 | # time to run the iteration 204 | x, u = S.solve() 205 | 206 | # the following code provides an animation of the system above 207 | # for a more detailed explanation have a look at the 'Visualisation' section in the documentation 208 | import sys 209 | import matplotlib as mpl 210 | from pytrajectory.visualisation import Animation 211 | 212 | def draw(xt, image): 213 | x = xt[0] 214 | phi1 = xt[2] 215 | phi2 = xt[4] 216 | 217 | car_width = 0.05 218 | car_heigth = 0.02 219 | 220 | rod_length = 2.0 * 0.25 221 | pendulum_size = 0.015 222 | 223 | x_car = x 224 | y_car = 0 225 | 226 | x_pendulum1 = x_car + rod_length * sin(phi1) 227 | y_pendulum1 = rod_length * cos(phi1) 228 | 229 | x_pendulum2 = x_pendulum1 + rod_length * sin(phi2) 230 | y_pendulum2 = y_pendulum1 + rod_length * cos(phi2) 231 | 232 | # create image 233 | pendulum1 = mpl.patches.Circle(xy=(x_pendulum1, y_pendulum1), radius=pendulum_size, color='black') 234 | pendulum2 = mpl.patches.Circle(xy=(x_pendulum2, y_pendulum2), radius=pendulum_size, color='black') 235 | 236 | car = mpl.patches.Rectangle((x_car-0.5*car_width, y_car-car_heigth), car_width, car_heigth, 237 | fill=True, facecolor='grey', linewidth=2.0) 238 | joint = mpl.patches.Circle((x_car,0), 0.005, color='black') 239 | 240 | rod1 = mpl.lines.Line2D([x_car,x_pendulum1], [y_car,y_pendulum1], 241 | color='black', zorder=1, linewidth=2.0) 242 | rod2 = mpl.lines.Line2D([x_pendulum1,x_pendulum2], [y_pendulum1,y_pendulum2], 243 | color='black', zorder=1, linewidth=2.0) 244 | 245 | # add the patches and lines to the image 246 | image.patches.append(pendulum1) 247 | image.patches.append(pendulum2) 248 | image.patches.append(car) 249 | image.patches.append(joint) 250 | image.lines.append(rod1) 251 | image.lines.append(rod2) 252 | 253 | # and return the image 254 | return image 255 | 256 | if not 'no-pickle' in sys.argv: 257 | # here we save the simulation results so we don't have to run 258 | # the iteration again in case the following fails 259 | S.save(fname='ex8_ConstrainedDoublePendulum.pcl') 260 | 261 | if 'plot' in sys.argv or 'animate' in sys.argv: 262 | # create Animation object 263 | A = Animation(drawfnc=draw, simdata=S.sim_data, 264 | plotsys=[(0,'$x$'),(1,'$\\dot{x}$')], plotinputs=[(0,'$u$')]) 265 | xmin = np.min(S.sim_data[1][:,0]) 266 | xmax = np.max(S.sim_data[1][:,0]) 267 | A.set_limits(xlim=(xmin - 1.0, xmax + 1.0), ylim=(-1.2,1.2)) 268 | 269 | if 'plot' in sys.argv: 270 | A.show(t=S.b) 271 | 272 | if 'animate' in sys.argv: 273 | A.animate() 274 | A.save('ex8_ConstrainedDoublePendulum.gif') 275 | -------------------------------------------------------------------------------- /examples/ex9_TriplePendulum.py: -------------------------------------------------------------------------------- 1 | # 3-bar pendulum 2 | 3 | # import all we need for solving the problem 4 | from pytrajectory import ControlSystem 5 | 6 | import numpy as np 7 | import sympy as sp 8 | 9 | from sympy import cos, sin 10 | from numpy import pi 11 | 12 | def n_bar_pendulum(N=1, param_values=dict()): 13 | ''' 14 | Returns the mass matrix :math:`M` and right hand site :math:`B` of motion equations 15 | 16 | .. math:: 17 | M * (d^2/dt^2) x = B 18 | 19 | for the :math:`N`\ -bar pendulum. 20 | 21 | Parameters 22 | ---------- 23 | 24 | N : int 25 | Number of bars. 26 | 27 | param_values : dict 28 | Numeric values for the system parameters, 29 | such as lengths, masses and gravitational acceleration. 30 | 31 | Returns 32 | ------- 33 | 34 | sympy.Matrix 35 | The mass matrix `M` 36 | 37 | sympy.Matrix 38 | The right hand site `B` 39 | 40 | list 41 | List of symbols for state variables 42 | 43 | list 44 | List with symbol for input variable 45 | ''' 46 | 47 | # first we have to create some symbols 48 | F = sp.Symbol('F') # the force that acts on the car 49 | g = sp.Symbol('g') # the gravitational acceleration 50 | m = sp.symarray('m', N+1) # masses of the car (`m0`) and the bars 51 | l = sp.symarray('l', N+1)#[1:] # length of the bars (`l0` is not needed nor used) 52 | phi = sp.symarray('phi', N+1)#[1:] # deflaction angles of the bars (`phi0` is not needed nor used) 53 | dphi = sp.symarray('dphi', N+1)#[1:] # 1st derivative of the deflaction angles (`dphi0` is not needed nor used) 54 | 55 | if param_values.has_key('F'): 56 | F = param_values['F'] 57 | elif param_values.has_key(F): 58 | F = param_values[F] 59 | 60 | if param_values.has_key('g'): 61 | g = param_values['g'] 62 | elif param_values.has_key(g): 63 | g = param_values[g] 64 | else: 65 | g = 9.81 66 | 67 | for i, mi in enumerate(m): 68 | if param_values.has_key(mi.name): 69 | m[i] = param_values[mi.name] 70 | elif param_values.has_key(mi): 71 | m[i] = param_values[mi] 72 | 73 | for i, li in enumerate(l): 74 | if param_values.has_key(li.name): 75 | l[i] = param_values[li.name] 76 | elif param_values.has_key(li): 77 | l[i] = param_values[li] 78 | 79 | C = np.empty((N,N), dtype=object) 80 | S = np.empty((N,N), dtype=object) 81 | I = np.empty((N), dtype=object) 82 | for i in xrange(1,N+1): 83 | for j in xrange(1,N+1): 84 | C[i-1,j-1] = cos(phi[i] - phi[j]) 85 | S[i-1,j-1] = sin(phi[i] - phi[j]) 86 | 87 | for i in xrange(1,N+1): 88 | if param_values.has_key('I_%d'%i): 89 | I[i-1] = param_values['I_%d'%i] 90 | #elif param_values.has_key(Ii): 91 | # I[i] = param_values[Ii] 92 | else: 93 | I[i-1] = 4.0/3.0 * m[i] * l[i]**2 94 | 95 | #-------------# 96 | # Mass matrix # 97 | #-------------# 98 | M = np.empty((N+1, N+1), dtype=object) 99 | 100 | # 1st row 101 | M[0,0] = m.sum() 102 | for j in xrange(1,N): 103 | M[0,j] = (m[j] + 2*m[j+1:].sum()) * l[j] * cos(phi[j]) 104 | M[0,N] = m[N] * l[N] * cos(phi[N]) 105 | 106 | # rest of upper triangular part, except last column 107 | for i in xrange(1,N): 108 | M[i,i] = I[i-1] + (m[i] + 4.0*m[i+1:].sum()) * l[i]**2 109 | for j in xrange(i+1,N): 110 | M[i,j] = 2.0*(m[j] + 2.0*m[j+1:].sum())*l[i]*l[j]*C[j-1,i-1] 111 | 112 | # the last column 113 | for i in xrange(1,N): 114 | M[i,N] = 2.0*(m[N]*l[i]*l[N]*C[N-1,i-1]) 115 | M[N,N] = I[N-1] + m[N]*l[N]**2 116 | 117 | # the rest (lower triangular part) 118 | for i in xrange(N+1): 119 | for j in xrange(i,N+1): 120 | M[j,i] = 1 * M[i,j] 121 | 122 | #-----------------# 123 | # Right hand site # 124 | #-----------------# 125 | B = np.empty((N+1), dtype=object) 126 | 127 | # first row 128 | B[0] = F 129 | for j in xrange(1,N): 130 | B[0] += (m[j] + 2.0*m[j+1:].sum())*l[j]*sin(phi[j]) * dphi[j]**2 131 | B[0] += (m[N]*l[N]*sin(phi[N])) * dphi[N]**2 132 | 133 | # rest except for last row 134 | for i in xrange(1,N): 135 | B[i] = (m[i] + 2.0*m[i+1:].sum())*g*l[i]*sin(phi[i]) 136 | for j in xrange(1,N): 137 | B[i] += (2.0*(m[j] + 2.0*m[j+1:].sum())*l[j]*l[i]*S[j-1,i-1]) * dphi[j]**2 138 | B[i] += (2.0*m[N]*l[N]*l[N]*S[N-1,i-1]) * dphi[N]**2 139 | 140 | # last row 141 | B[N] = m[N]*g*l[N]*sin(phi[N]) 142 | for j in xrange(1,N+1): 143 | B[N] += (2.0*m[N]*l[j]*l[N]*S[j-1,N-1]) * dphi[j]**2 144 | 145 | # build lists of state and input variables 146 | x, dx = sp.symbols('x, dx') 147 | state_vars = [x, dx] 148 | for i in xrange(1,N+1): 149 | state_vars.append(phi[i]) 150 | state_vars.append(dphi[i]) 151 | input_vars = [F] 152 | 153 | # return stuff 154 | return sp.Matrix(M), sp.Matrix(B), state_vars, input_vars 155 | 156 | def solve_motion_equations(M, B, state_vars=[], input_vars=[], parameters_values=dict()): 157 | ''' 158 | Solves the motion equations given by the mass matrix and right hand side 159 | to define a callable function for the vector field of the respective 160 | control system. 161 | 162 | Parameters 163 | ---------- 164 | 165 | M : sympy.Matrix 166 | A sympy.Matrix containing sympy expressions and symbols that represent 167 | the mass matrix of the control system. 168 | 169 | B : sympy.Matrix 170 | A sympy.Matrix containing sympy expressions and symbols that represent 171 | the right hand site of the motion equations. 172 | 173 | state_vars : list 174 | A list with sympy.Symbols's for each state variable. 175 | 176 | input_vars : list 177 | A list with sympy.Symbols's for each input variable. 178 | 179 | parameter_values : dict 180 | A dictionary with a key:value pair for each system parameter. 181 | 182 | Returns 183 | ------- 184 | 185 | callable 186 | A callable function for the vectorfield. 187 | ''' 188 | 189 | M_shape = M.shape 190 | B_shape = B.shape 191 | assert(M_shape[0] == B_shape[0]) 192 | 193 | # at first we create a buffer for the string that we complete and execute 194 | # to dynamically define a function and return it 195 | fnc_str_buffer =''' 196 | def f(x, u): 197 | # System variables 198 | %s # x_str 199 | %s # u_str 200 | 201 | # Parameters 202 | %s # par_str 203 | 204 | # Sympy Common Expressions 205 | %s # cse_str 206 | 207 | # Vectorfield 208 | %s # ff_str 209 | 210 | return ff 211 | ''' 212 | 213 | ########################################### 214 | # handle system state and input variables # 215 | ########################################### 216 | # --> leads to x_str and u_str which show how to unpack the variables 217 | x_str = '' 218 | u_str = '' 219 | 220 | for var in state_vars: 221 | x_str += '%s, '%str(var) 222 | 223 | for var in input_vars: 224 | u_str += '%s, '%str(var) 225 | 226 | x_str = x_str + '= x' 227 | u_str = u_str + '= u' 228 | 229 | ############################ 230 | # handle system parameters # 231 | ############################ 232 | # --> leads to par_str 233 | par_str = '' 234 | for k, v in parameters_values.items(): 235 | # 'k' is the name of a system parameter such as mass or gravitational acceleration 236 | # 'v' is its value in SI units 237 | par_str += '%s = %s; '%(str(k), str(v)) 238 | 239 | # as a last we remove the trailing '; ' from par_str to avoid syntax errors 240 | par_str = par_str[:-2] 241 | 242 | # now solve the motion equations w.r.t. the accelerations 243 | # (might take some while...) 244 | #print " -> solving motion equations w.r.t. accelerations" 245 | 246 | # apply sympy.cse() on M and B to speed up solving the eqs 247 | M_cse_list, M_cse_res = sp.cse(M, symbols=sp.numbered_symbols('M_cse')) 248 | B_cse_list, B_cse_res = sp.cse(B, symbols=sp.numbered_symbols('B_cse')) 249 | 250 | # solve abbreviated equation system 251 | #sol = M.solve(B) 252 | Mse = M_cse_res[0] 253 | Bse = B_cse_res[0] 254 | cse_sol = Mse.solve(Bse) 255 | 256 | # substitute back the common subexpressions to the solution 257 | for expr in reversed(B_cse_list): 258 | cse_sol = cse_sol.subs(*expr) 259 | 260 | for expr in reversed(M_cse_list): 261 | cse_sol = cse_sol.subs(*expr) 262 | 263 | # use SymPy's Common Subexpression Elimination 264 | #cse_list, cse_res = sp.cse(sol, symbols=sp.numbered_symbols('q')) 265 | cse_list, cse_res = sp.cse(cse_sol, symbols=sp.numbered_symbols('q')) 266 | 267 | ################################ 268 | # handle common subexpressions # 269 | ################################ 270 | # --> leads to cse_str 271 | cse_str = '' 272 | #cse_list = [(str(l), str(r)) for l, r in cse_list] 273 | for cse_pair in cse_list: 274 | cse_str += '%s = %s; '%(str(cse_pair[0]), str(cse_pair[1])) 275 | 276 | # add result of cse 277 | for i in xrange(M_shape[0]): 278 | cse_str += 'q%d_dd = %s; '%(i, str(cse_res[0][i])) 279 | 280 | cse_str = cse_str[:-2] 281 | 282 | ###################### 283 | # create vectorfield # 284 | ###################### 285 | # --> leads to ff_str 286 | ff_str = 'ff = [' 287 | 288 | for i in xrange(M_shape[0]): 289 | ff_str += '%s, '%str(state_vars[2*i+1]) 290 | ff_str += 'q%s_dd, '%(i) 291 | 292 | # remove trailing ',' and add closing brackets 293 | ff_str = ff_str[:-2] + ']' 294 | 295 | ############################ 296 | # Create callable function # 297 | ############################ 298 | # now we can replace all placeholders in the function string buffer 299 | fnc_str = fnc_str_buffer%(x_str, u_str, par_str, cse_str, ff_str) 300 | # and finally execute it which will create a python function 'f' 301 | exec(fnc_str) 302 | 303 | # now we have defined a callable function that can be used within PyTrajectory 304 | return f 305 | 306 | # we consider the case of a 3-bar pendulum 307 | N = 3 308 | 309 | # set model parameters 310 | l1 = 0.25 # 1/2 * length of the pendulum 1 311 | l2 = 0.25 # 1/2 * length of the pendulum 2 312 | l3 = 0.25 # 1/2 * length of the pendulum 3 313 | m1 = 0.1 # mass of the pendulum 1 314 | m2 = 0.1 # mass of the pendulum 2 315 | m3 = 0.1 # mass of the pendulum 3 316 | m = 1.0 # mass of the car 317 | g = 9.81 # gravitational acceleration 318 | I1 = 4.0/3.0 * m1 * l1**2 # inertia 1 319 | I2 = 4.0/3.0 * m2 * l2**2 # inertia 2 320 | I3 = 4.0/3.0 * m2 * l2**2 # inertia 3 321 | 322 | param_values = {'l_1':l1, 'l_2':l2, 'l_3':l3, 323 | 'm_1':m1, 'm_2':m2, 'm_3':m3, 324 | 'm_0':m, 'g':g, 325 | 'I_1':I1, 'I_2':I2, 'I_3':I3} 326 | 327 | # get matrices of motion equations 328 | M, B, state_vars, input_vars = n_bar_pendulum(N=3, param_values=param_values) 329 | 330 | # get callable function for vectorfield that can be used with PyTrajectory 331 | f = solve_motion_equations(M, B, state_vars, input_vars) 332 | 333 | # then we specify all boundary conditions 334 | a = 0.0 335 | xa = [0.0, 0.0, pi, 0.0, pi, 0.0, pi, 0.0] 336 | 337 | b = 3.5 338 | xb = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] 339 | 340 | ua = [0.0] 341 | ub = [0.0] 342 | 343 | # now we create our Trajectory object and alter some method parameters via the keyword arguments 344 | S = ControlSystem(f, a, b, xa, xb, ua, ub, constraints=None, 345 | eps=4e-1, su=30, kx=2, use_chains=False, 346 | use_std_approach=False) 347 | 348 | # time to run the iteration 349 | x, u = S.solve() 350 | 351 | # the following code provides an animation of the system above 352 | # for a more detailed explanation have a look at the 'Visualisation' section 353 | # in the documentation 354 | import sys 355 | import matplotlib as mpl 356 | from pytrajectory.visualisation import Animation 357 | 358 | # all rods have the same length 359 | rod_lengths = [0.5] * N 360 | 361 | # all pendulums have the same size 362 | pendulum_sizes = [0.015] * N 363 | 364 | car_width, car_height = [0.05, 0.02] 365 | 366 | # the drawing function 367 | def draw(xt, image): 368 | x = xt[0] 369 | phi = xt[2::2] 370 | 371 | x_car = x 372 | y_car = 0 373 | 374 | # coordinates of the pendulums 375 | x_p = [] 376 | y_p = [] 377 | 378 | # first pendulum 379 | x_p.append( x_car + rod_lengths[0] * sin(phi[0]) ) 380 | y_p.append( rod_lengths[0] * cos(phi[0]) ) 381 | 382 | # the rest 383 | for i in xrange(1,3): 384 | x_p.append( x_p[i-1] + rod_lengths[i] * sin(phi[i]) ) 385 | y_p.append( y_p[i-1] + rod_lengths[i] * cos(phi[i]) ) 386 | 387 | # create image 388 | 389 | # first the car and joint 390 | car = mpl.patches.Rectangle((x_car-0.5*car_width, y_car-car_height), car_width, car_height, 391 | fill=True, facecolor='grey', linewidth=2.0) 392 | joint = mpl.patches.Circle((x_car,0), 0.005, color='black') 393 | 394 | image.patches.append(car) 395 | image.patches.append(joint) 396 | 397 | # then the pendulums 398 | for i in xrange(3): 399 | image.patches.append( mpl.patches.Circle(xy=(x_p[i], y_p[i]), 400 | radius=pendulum_sizes[i], 401 | color='black') ) 402 | 403 | if i == 0: 404 | image.lines.append( mpl.lines.Line2D(xdata=[x_car, x_p[0]], ydata=[y_car, y_p[0]], 405 | color='black', zorder=1, linewidth=2.0) ) 406 | else: 407 | image.lines.append( mpl.lines.Line2D(xdata=[x_p[i-1], x_p[i]], ydata=[y_p[i-1], y_p[i]], 408 | color='black', zorder=1, linewidth=2.0) ) 409 | # and return the image 410 | return image 411 | 412 | # create Animation object 413 | if 'plot' in sys.argv or 'animate' in sys.argv: 414 | A = Animation(drawfnc=draw, simdata=S.sim_data, 415 | plotsys=[(0,'$x$'),(1,'$\\dot{x}$')], plotinputs=[(0,'$u$')]) 416 | xmin = np.min(S.sim_data[1][:,0]) 417 | xmax = np.max(S.sim_data[1][:,0]) 418 | A.set_limits(xlim=(xmin - 1.5, xmax + 1.5), ylim=(-2.0,2.0)) 419 | 420 | if 'plot' in sys.argv: 421 | A.show(t=S.b) 422 | 423 | if 'animate' in sys.argv: 424 | A.animate() 425 | A.save('ex9_TriplePendulum.gif') 426 | -------------------------------------------------------------------------------- /pytrajectory/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | PyTrajectory 3 | ============ 4 | 5 | PyTrajectory is a Python library for the determination of the feed forward control 6 | to achieve a transition between desired states of a nonlinear control system. 7 | ''' 8 | 9 | from system import ControlSystem 10 | from trajectories import Trajectory 11 | from splines import Spline 12 | from solver import Solver 13 | from simulation import Simulator 14 | from visualisation import Animation 15 | from log import logging 16 | 17 | # current version 18 | __version__ = '1.2.0' 19 | 20 | # Placeholder for the datetime string of latest commit 21 | __date__ = "2016-01-15 14:12:08" 22 | 23 | # `__date__` contains the date and time of the latest commit 24 | # (will be altered with every commit using git's pre-commit hook) 25 | 26 | # check versions of dependencies 27 | import numpy 28 | import scipy 29 | import sympy 30 | 31 | np_info = numpy.__version__.split('.') 32 | scp_info = scipy.__version__.split('.') 33 | sp_info = sympy.__version__.split('.') 34 | 35 | if not (int(np_info[0]) >= 1 and int(np_info[1]) >= 8): 36 | logging.warning('numpy version ({}) may be out of date'.format(numpy.__version__)) 37 | if not (int(scp_info[0]) >= 0 and int(scp_info[1]) >= 13 and int(scp_info[2][0]) >= 0): 38 | logging.warning('scipy version ({}) may be out of date'.format(scipy.__version__)) 39 | if not (int(sp_info[0]) >= 0 and int(sp_info[1]) >= 7 and int(sp_info[2][0]) >= 5): 40 | logging.warning('sympy version ({}) may be out of date'.format(sympy.__version__)) 41 | 42 | # log information about current version 43 | logging.debug('This is PyTrajectory version {} of {}'.format(__version__, __date__)) 44 | 45 | -------------------------------------------------------------------------------- /pytrajectory/log.py: -------------------------------------------------------------------------------- 1 | import time 2 | import sys 3 | import os 4 | 5 | import logging 6 | 7 | DEBUG = False 8 | LOG2CONSOLE = True 9 | LOG2FILE = False 10 | 11 | # get logger 12 | logger = logging.getLogger() 13 | logger.setLevel(logging.DEBUG) 14 | 15 | # log to console 16 | if LOG2CONSOLE: 17 | console_handler = logging.StreamHandler() 18 | console_formatter = logging.Formatter(fmt='%(levelname)s: \t %(message)s', datefmt='%d-%m-%Y %H:%M:%S') 19 | 20 | if DEBUG: 21 | console_level = logging.DEBUG 22 | else: 23 | console_level = logging.INFO 24 | 25 | console_handler.setFormatter(console_formatter) 26 | console_handler.setLevel(console_level) 27 | 28 | logger.addHandler(console_handler) 29 | 30 | # log to file 31 | if LOG2FILE: 32 | try: 33 | fname = sys.argv[0].split('.')[0]+"_"+time.strftime('%y%m%d-%H%M%S')+".log" 34 | 35 | file_handler = logging.FileHandler(filename=fname, mode='a') 36 | file_formatter = logging.Formatter(fmt='%(asctime)s %(levelname)s: \t %(message)s', datefmt='%d-%m-%Y %H:%M:%S') 37 | file_level = logging.DEBUG 38 | 39 | file_handler.setFormatter(file_formatter) 40 | file_handler.setLevel(file_level) 41 | 42 | logger.addHandler(file_handler) 43 | except Exception as err: 44 | logging.error('Could not create log file!') 45 | logging.error('Got message: {}'.format(err)) 46 | 47 | 48 | class Timer(): 49 | ''' 50 | Provides a context manager that takes the time of a code block. 51 | 52 | Parameters 53 | ---------- 54 | 55 | label : str 56 | The 'name' of the code block which is timed 57 | 58 | verb : int 59 | Level of verbosity 60 | ''' 61 | def __init__(self, label="~", verb=4): 62 | self.label = label 63 | self.verb = verb 64 | 65 | def __enter__(self): 66 | self.start = time.time() 67 | 68 | def __exit__(self, *args): 69 | self.delta = time.time() - self.start 70 | logging.debug("---> [%s elapsed %f s]"%(self.label, self.delta)) 71 | -------------------------------------------------------------------------------- /pytrajectory/simulation.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.integrate import ode 3 | 4 | class Simulator(object): 5 | ''' 6 | This class simulates the initial value problem that results from solving 7 | the boundary value problem of the control system. 8 | 9 | 10 | Parameters 11 | ---------- 12 | 13 | ff : callable 14 | Vectorfield of the control system. 15 | 16 | T : float 17 | Simulation time. 18 | 19 | u : callable 20 | Function of the input variables. 21 | 22 | dt : float 23 | Time step. 24 | ''' 25 | 26 | def __init__(self, ff, T, start, u, dt=0.01): 27 | self.ff = ff 28 | self.T = T 29 | self.u = u 30 | self.dt = dt 31 | 32 | # this is where the solutions go 33 | self.xt = [] 34 | self.ut = [] 35 | 36 | # time steps 37 | self.t = [] 38 | 39 | # get the values at t=0 40 | self.xt.append(start) 41 | self.ut.append(self.u(0.0)) 42 | self.t.append(0.0) 43 | 44 | #initialise our ode solver 45 | self.solver = ode(self.rhs) 46 | self.solver.set_initial_value(start) 47 | self.solver.set_integrator('vode', method='adams', rtol=1e-6) 48 | #self.solver.set_integrator('lsoda', rtol=1e-6) 49 | #self.solver.set_integrator('dop853', rtol=1e-6) 50 | 51 | 52 | def rhs(self, t, x): 53 | ''' 54 | Retruns the right hand side (vector field) of the ode system. 55 | ''' 56 | u = self.u(t) 57 | dx = self.ff(x, u) 58 | 59 | return dx 60 | 61 | 62 | def calcStep(self): 63 | ''' 64 | Calculates one step of the simulation. 65 | ''' 66 | x = list(self.solver.integrate(self.solver.t+self.dt)) 67 | t = round(self.solver.t, 5) 68 | 69 | if 0 <= t <= self.T: 70 | self.xt.append(x) 71 | self.ut.append(self.u(t)) 72 | self.t.append(t) 73 | 74 | return t, x 75 | 76 | def simulate(self): 77 | ''' 78 | Starts the simulation 79 | 80 | 81 | Returns 82 | ------- 83 | 84 | List of numpy arrays with time steps and simulation data of system and input variables. 85 | ''' 86 | t = 0 87 | while t <= self.T: 88 | t, y = self.calcStep() 89 | return [np.array(self.t), np.array(self.xt), np.array(self.ut)] 90 | -------------------------------------------------------------------------------- /pytrajectory/solver.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.linalg import solve, norm 3 | import scipy as scp 4 | 5 | from log import logging 6 | 7 | 8 | 9 | class Solver: 10 | ''' 11 | This class provides solver for the collocation equation system. 12 | 13 | 14 | Parameters 15 | ---------- 16 | 17 | F : callable 18 | The callable function that represents the equation system 19 | 20 | DF : callable 21 | The function for the jacobian matrix of the eqs 22 | 23 | x0: numpy.ndarray 24 | The start value for the sover 25 | 26 | tol : float 27 | The (absolute) tolerance of the solver 28 | 29 | maxIt : int 30 | The maximum number of iterations of the solver 31 | 32 | method : str 33 | The solver to use 34 | ''' 35 | 36 | def __init__(self, F, DF, x0, tol=1e-5, maxIt=100, method='leven'): 37 | self.F = F 38 | self.DF = DF 39 | self.x0 = x0 40 | self.tol = tol 41 | self.reltol = 2e-5 42 | self.maxIt = maxIt 43 | self.method = method 44 | 45 | self.sol = None 46 | 47 | 48 | def solve(self): 49 | ''' 50 | This is just a wrapper to call the chosen algorithm for solving the 51 | collocation equation system. 52 | ''' 53 | 54 | if (self.method == 'leven'): 55 | logging.debug("Run Levenberg-Marquardt method") 56 | self.leven() 57 | 58 | if (self.sol is None): 59 | logging.warning("Wrong solver, returning initial value.") 60 | return self.x0 61 | else: 62 | return self.sol 63 | 64 | 65 | def leven(self): 66 | ''' 67 | This method is an implementation of the Levenberg-Marquardt-Method 68 | to solve nonlinear least squares problems. 69 | 70 | For more information see: :ref:`levenberg_marquardt` 71 | ''' 72 | i = 0 73 | x = self.x0 74 | res = 1 75 | res_alt = -1 76 | 77 | eye = scp.sparse.identity(len(self.x0)) 78 | 79 | #mu = 1.0 80 | mu = 1e-4 81 | 82 | # borders for convergence-control 83 | b0 = 0.2 84 | b1 = 0.8 85 | 86 | roh = 0.0 87 | 88 | reltol = self.reltol 89 | 90 | Fx = self.F(x) 91 | 92 | while((res > self.tol) and (self.maxIt > i) and (abs(res-res_alt) > reltol)): 93 | i += 1 94 | 95 | #if (i-1)%4 == 0: 96 | DFx = self.DF(x) 97 | DFx = scp.sparse.csr_matrix(DFx) 98 | 99 | while (roh < b0): 100 | A = DFx.T.dot(DFx) + mu**2*eye 101 | 102 | b = DFx.T.dot(Fx) 103 | 104 | s = -scp.sparse.linalg.spsolve(A,b) 105 | 106 | xs = x + np.array(s).flatten() 107 | 108 | Fxs = self.F(xs) 109 | 110 | normFx = norm(Fx) 111 | normFxs = norm(Fxs) 112 | 113 | roh = (normFx**2 - normFxs**2) / (normFx**2 - (norm(Fx+DFx.dot(s)))**2) 114 | 115 | if (roh<=b0): mu = 2.0*mu 116 | if (roh>=b1): mu = 0.5*mu 117 | #logging.debug(" roh= %f mu= %f"%(roh,mu)) 118 | logging.debug(' mu = {}'.format(mu)) 119 | 120 | # the following was believed to be some kind of bug, hence the warning 121 | # but that was not the case... 122 | #if (roh < 0.0): 123 | #log.warn("Parameter roh in LM-method became negative", verb=3) 124 | #from IPython import embed as IPS 125 | #IPS() 126 | 127 | Fx = Fxs 128 | x = xs 129 | 130 | roh = 0.0 131 | res_alt = res 132 | res = normFx 133 | logging.debug("nIt= %d res= %f"%(i,res)) 134 | 135 | # NEW - experimental 136 | #if res<1.0: 137 | # reltol = 1e-3 138 | 139 | self.sol = x 140 | -------------------------------------------------------------------------------- /pytrajectory/trajectories.py: -------------------------------------------------------------------------------- 1 | # IMPORTS 2 | import numpy as np 3 | import copy 4 | 5 | from splines import Spline, differentiate 6 | from log import logging 7 | import auxiliary 8 | 9 | class Trajectory(object): 10 | ''' 11 | This class handles the creation and managing of the spline functions 12 | that are intended to approximate the desired trajectories. 13 | 14 | Parameters 15 | ---------- 16 | 17 | sys : system.DynamicalSystem 18 | Instance of a dynamical system providing information like 19 | vector field function and boundary values 20 | ''' 21 | 22 | def __init__(self, sys, **kwargs): 23 | # save the dynamical system 24 | self.sys = sys 25 | 26 | # set parameters 27 | self._parameters = dict() 28 | self._parameters['n_parts_x'] = kwargs.get('sx', 10) 29 | self._parameters['n_parts_u'] = kwargs.get('su', 10) 30 | self._parameters['kx'] = kwargs.get('kx', 2) 31 | self._parameters['nodes_type'] = kwargs.get('nodes_type', 'equidistant') 32 | self._parameters['use_std_approach'] = kwargs.get('use_std_approach', True) 33 | 34 | self._chains, self._eqind = auxiliary.find_integrator_chains(sys) 35 | self._parameters['use_chains'] = kwargs.get('use_chains', True) 36 | 37 | # Initialise dictionaries as containers for all 38 | # spline functions that will be created 39 | self.splines = dict() 40 | self.x_fnc = dict() 41 | self.u_fnc = dict() 42 | self.dx_fnc = dict() 43 | 44 | # This will be the free parameters of the control problem 45 | # (list of all independent spline coefficients) 46 | self.indep_coeffs = [] 47 | 48 | self._old_splines = None 49 | 50 | @property 51 | def n_parts_x(self): 52 | ''' 53 | Number of polynomial spline parts for system variables. 54 | ''' 55 | return self._parameters['n_parts_x'] 56 | 57 | @property 58 | def n_parts_u(self): 59 | ''' 60 | Number of polynomial spline parts for input variables. 61 | ''' 62 | return self._parameters['n_parts_u'] 63 | 64 | def _raise_spline_parts(self, k=None): 65 | if k is not None: 66 | self._parameters['n_parts_x'] *= int(k) 67 | else: 68 | self._parameters['n_parts_x'] *= self._parameters['kx'] 69 | 70 | return self.n_parts_x 71 | 72 | def x(self, t): 73 | ''' 74 | Returns the current system state. 75 | 76 | Parameters 77 | ---------- 78 | 79 | t : float 80 | The time point in (a,b) to evaluate the system at. 81 | ''' 82 | 83 | if not self.sys.a <= t <= self.sys.b: 84 | logging.warning("Time point 't' has to be in (a,b)") 85 | arr = None 86 | else: 87 | arr = np.array([self.x_fnc[xx](t) for xx in self.sys.states]) 88 | 89 | return arr 90 | 91 | def u(self, t): 92 | ''' 93 | Returns the state of the input variables. 94 | 95 | Parameters 96 | ---------- 97 | 98 | t : float 99 | The time point in (a,b) to evaluate the input variables at. 100 | ''' 101 | 102 | if not self.sys.a <= t <= self.sys.b: 103 | #logging.warning("Time point 't' has to be in (a,b)") 104 | arr = np.array([self.u_fnc[uu](self.sys.b) for uu in self.sys.inputs]) 105 | else: 106 | arr = np.array([self.u_fnc[uu](t) for uu in self.sys.inputs]) 107 | 108 | return arr 109 | 110 | def dx(self, t): 111 | ''' 112 | Returns the state of the 1st derivatives of the system variables. 113 | 114 | Parameters 115 | ---------- 116 | 117 | t : float 118 | The time point in (a,b) to evaluate the 1st derivatives at. 119 | ''' 120 | 121 | if not self.sys.a <= t <= self.sys.b: 122 | logging.warning("Time point 't' has to be in (a,b)") 123 | arr = None 124 | else: 125 | arr = np.array([self.dx_fnc[xx](t) for xx in self.sys.states]) 126 | 127 | return arr 128 | 129 | def init_splines(self): 130 | ''' 131 | This method is used to create the necessary spline function objects. 132 | 133 | Parameters 134 | ---------- 135 | 136 | boundary_values : dict 137 | Dictionary of boundary values for the state and input splines functions. 138 | 139 | ''' 140 | logging.debug("Initialise Splines") 141 | 142 | # store the old splines to calculate the guess later 143 | self._old_splines = copy.deepcopy(self.splines) 144 | 145 | bv = self.sys.boundary_values 146 | 147 | # dictionaries for splines and callable solution function for x,u and dx 148 | splines = dict() 149 | x_fnc = dict() 150 | u_fnc = dict() 151 | dx_fnc = dict() 152 | 153 | if self._parameters['use_chains']: 154 | # first handle variables that are part of an integrator chain 155 | for chain in self._chains: 156 | upper = chain.upper 157 | lower = chain.lower 158 | 159 | # here we just create a spline object for the upper ends of every chain 160 | # w.r.t. its lower end (whether it is an input variable or not) 161 | if chain.lower.startswith('x'): 162 | splines[upper] = Spline(self.sys.a, self.sys.b, n=self.n_parts_x, bv={0:bv[upper]}, tag=upper, 163 | nodes_type=self._parameters['nodes_type'], 164 | use_std_approach=self._parameters['use_std_approach']) 165 | splines[upper].type = 'x' 166 | elif chain.lower.startswith('u'): 167 | splines[upper] = Spline(self.sys.a, self.sys.b, n=self.n_parts_u, bv={0:bv[lower]}, tag=upper, 168 | nodes_type=self._parameters['nodes_type'], 169 | use_std_approach=self._parameters['use_std_approach']) 170 | splines[upper].type = 'u' 171 | 172 | # search for boundary values to satisfy 173 | for i, elem in enumerate(chain.elements): 174 | if elem in self.sys.states: 175 | splines[upper]._boundary_values[i] = bv[elem] 176 | if splines[upper].type == 'u': 177 | splines[upper]._boundary_values[i+1] = bv[lower] 178 | 179 | # solve smoothness and boundary conditions 180 | splines[upper].make_steady() 181 | 182 | # calculate derivatives 183 | for i, elem in enumerate(chain.elements): 184 | if elem in self.sys.inputs: 185 | if (i == 0): 186 | u_fnc[elem] = splines[upper].f 187 | if (i == 1): 188 | u_fnc[elem] = splines[upper].df 189 | if (i == 2): 190 | u_fnc[elem] = splines[upper].ddf 191 | elif elem in self.sys.states: 192 | if (i == 0): 193 | splines[upper]._boundary_values[0] = bv[elem] 194 | if splines[upper].type == 'u': 195 | splines[upper]._boundary_values[1] = bv[lower] 196 | x_fnc[elem] = splines[upper].f 197 | if (i == 1): 198 | splines[upper]._boundary_values[1] = bv[elem] 199 | if splines[upper].type == 'u': 200 | splines[upper]._boundary_values[2] = bv[lower] 201 | x_fnc[elem] = splines[upper].df 202 | if (i == 2): 203 | splines[upper]._boundary_values[2] = bv[elem] 204 | x_fnc[elem] = splines[upper].ddf 205 | 206 | # now handle the variables which are not part of any chain 207 | for i, xx in enumerate(self.sys.states): 208 | if not x_fnc.has_key(xx): 209 | splines[xx] = Spline(self.sys.a, self.sys.b, n=self.n_parts_x, bv={0:bv[xx]}, tag=xx, 210 | nodes_type=self._parameters['nodes_type'], 211 | use_std_approach=self._parameters['use_std_approach']) 212 | splines[xx].make_steady() 213 | splines[xx].type = 'x' 214 | x_fnc[xx] = splines[xx].f 215 | 216 | offset = self.sys.n_states 217 | for j, uu in enumerate(self.sys.inputs): 218 | if not u_fnc.has_key(uu): 219 | splines[uu] = Spline(self.sys.a, self.sys.b, n=self.n_parts_u, bv={0:bv[uu]}, tag=uu, 220 | nodes_type=self._parameters['nodes_type'], 221 | use_std_approach=self._parameters['use_std_approach']) 222 | splines[uu].make_steady() 223 | splines[uu].type = 'u' 224 | u_fnc[uu] = splines[uu].f 225 | 226 | # calculate derivatives of every state variable spline 227 | for xx in self.sys.states: 228 | dx_fnc[xx] = differentiate(x_fnc[xx]) 229 | 230 | indep_coeffs = dict() 231 | for ss in splines.keys(): 232 | indep_coeffs[ss] = splines[ss]._indep_coeffs 233 | 234 | self.indep_coeffs = indep_coeffs 235 | self.splines = splines 236 | self.x_fnc = x_fnc 237 | self.u_fnc = u_fnc 238 | self.dx_fnc = dx_fnc 239 | 240 | def set_coeffs(self, sol): 241 | ''' 242 | Set found numerical values for the independent parameters of each spline. 243 | 244 | This method is used to get the actual splines by using the numerical 245 | solutions to set up the coefficients of the polynomial spline parts of 246 | every created spline. 247 | 248 | Parameters 249 | ---------- 250 | 251 | sol : numpy.ndarray 252 | The solution vector for the free parameters, i.e. the independent coefficients. 253 | 254 | ''' 255 | # TODO: look for bugs here! 256 | logging.debug("Set spline coefficients") 257 | 258 | sol_bak = sol.copy() 259 | subs = dict() 260 | 261 | for k, v in sorted(self.indep_coeffs.items(), key=lambda (k, v): k): 262 | i = len(v) 263 | subs[k] = sol[:i] 264 | sol = sol[i:] 265 | 266 | if self._parameters['use_chains']: 267 | for var in self.sys.states + self.sys.inputs: 268 | for ic in self._chains: 269 | if var in ic: 270 | subs[var] = subs[ic.upper] 271 | 272 | # set numerical coefficients for each spline and derivative 273 | for k in self.splines.keys(): 274 | self.splines[k].set_coefficients(free_coeffs=subs[k]) 275 | 276 | # yet another dictionary for solution and coeffs 277 | coeffs_sol = dict() 278 | 279 | # used for indexing 280 | i = 0 281 | j = 0 282 | 283 | for k, v in sorted(self.indep_coeffs.items(), key=lambda (k, v): k): 284 | j += len(v) 285 | coeffs_sol[k] = sol_bak[i:j] 286 | i = j 287 | 288 | self.coeffs_sol = coeffs_sol 289 | 290 | def save(self): 291 | 292 | save = dict() 293 | 294 | # parameters 295 | save['parameters'] = self._parameters 296 | 297 | # splines 298 | save['splines'] = dict((var, spline.save()) for var, spline in self.splines.iteritems()) 299 | 300 | # sol 301 | save['coeffs_col'] = self.coeffs_sol 302 | 303 | return save 304 | 305 | -------------------------------------------------------------------------------- /pytrajectory/visualisation.py: -------------------------------------------------------------------------------- 1 | # IMPORTS 2 | import numpy as np 3 | import matplotlib as mpl 4 | #mpl.use('TKAgg') 5 | import matplotlib.pyplot as plt 6 | from matplotlib import animation 7 | from matplotlib.gridspec import GridSpec 8 | import os 9 | 10 | 11 | def plot_simulation(sim_data, H=[], fname=None): 12 | ''' 13 | This method provides graphics for each system variable, manipulated 14 | variable and error function and plots the solution of the simulation. 15 | 16 | Parameters 17 | ---------- 18 | 19 | sim_data : tuple 20 | Contains collocation points, and simulation results of system and input variables. 21 | 22 | H : dict 23 | Dictionary of the callable error functions 24 | 25 | fname : str 26 | If not None, plot will be saved as .png 27 | ''' 28 | 29 | t, xt, ut = sim_data 30 | n = xt.shape[1] 31 | m = ut.shape[1] 32 | 33 | z = n + m + len(H.keys()) 34 | z1 = np.floor(np.sqrt(z)) 35 | z2 = np.ceil(z/z1) 36 | 37 | plt.rcParams['figure.subplot.bottom']=.2 38 | plt.rcParams['figure.subplot.top']= .95 39 | plt.rcParams['figure.subplot.left']=.13 40 | plt.rcParams['figure.subplot.right']=.95 41 | 42 | plt.rcParams['font.size']=16 43 | 44 | plt.rcParams['legend.fontsize']=16 45 | #plt.rc('text', usetex=True) 46 | 47 | 48 | plt.rcParams['xtick.labelsize']=16 49 | plt.rcParams['ytick.labelsize']=16 50 | plt.rcParams['legend.fontsize']=20 51 | 52 | plt.rcParams['axes.titlesize']=26 53 | plt.rcParams['axes.labelsize']=26 54 | 55 | 56 | plt.rcParams['xtick.major.pad']='8' 57 | plt.rcParams['ytick.major.pad']='8' 58 | 59 | mm = 1./25.4 #mm to inch 60 | scale = 3 61 | fs = [100*mm*scale, 60*mm*scale] 62 | 63 | fff=plt.figure(figsize=fs, dpi=80) 64 | 65 | 66 | PP=1 67 | for i in xrange(n): 68 | plt.subplot(int(z1),int(z2),PP) 69 | PP+=1 70 | plt.plot(t,xt[:,i]) 71 | plt.xlabel(r'$t$') 72 | plt.title(r'$'+'x%d'%(i+1)+'(t)$') 73 | 74 | for i in xrange(m): 75 | plt.subplot(int(z1),int(z2),PP) 76 | PP+=1 77 | plt.plot(t,ut[:,i]) 78 | plt.xlabel(r'$t$') 79 | plt.title(r'$'+'u%d'%(i+1)+'(t)$') 80 | 81 | for hh in H: 82 | plt.subplot(int(z1),int(z2),PP) 83 | PP+=1 84 | plt.plot(t,H[hh]) 85 | plt.xlabel(r'$t$') 86 | plt.title(r'$H_'+str(hh+1)+'(t)$') 87 | 88 | plt.tight_layout() 89 | 90 | plt.show() 91 | 92 | if fname: 93 | if not fname.endswith('.png'): 94 | plt.savefig(fname+'.png') 95 | else: 96 | plt.savefig(fname) 97 | 98 | 99 | class Animation(): 100 | ''' 101 | Provides animation capabilities. 102 | 103 | Given a callable function that draws an image of the system state and smiulation data 104 | this class provides a method to created an animated representation of the system. 105 | 106 | 107 | Parameters 108 | ---------- 109 | 110 | drawfnc : callable 111 | Function that returns an image of the current system state according to :attr:`simdata` 112 | 113 | simdata : numpy.ndarray 114 | Array that contains simulation data (time, system states, input states) 115 | 116 | plotsys : list 117 | List of tuples with indices and labels of system variables that will be plotted along the picture 118 | 119 | plotinputs : list 120 | List of tuples with indices and labels of input variables that will be plotted along the picture 121 | ''' 122 | 123 | def __init__(self, drawfnc, simdata, plotsys=[], plotinputs=[]): 124 | self.fig = plt.figure() 125 | 126 | self.image = 0 127 | 128 | self.t = simdata[0] 129 | self.xt = simdata[1] 130 | self.ut = simdata[2] 131 | 132 | self.plotsys = plotsys 133 | self.plotinputs = plotinputs 134 | 135 | self.get_axes() 136 | 137 | self.axes['ax_img'].set_frame_on(True) 138 | self.axes['ax_img'].set_aspect('equal') 139 | self.axes['ax_img'].set_axis_bgcolor('w') 140 | 141 | self.nframes = int(round(24*(self.t[-1] - self.t[0]))) 142 | 143 | self.draw = drawfnc 144 | 145 | # set axis limits and labels of system curves 146 | #xlim = (0.0, self.t[-1] - self.t[0]) 147 | xlim = (self.t[0], self.t[-1]) 148 | for i, idxlabel in enumerate(self.plotsys): 149 | idx, label = idxlabel 150 | 151 | try: 152 | ylim = (min(self.xt[:,idx]), max(self.xt[:,idx])) 153 | except: 154 | ylim = (min(self.xt), max(self.xt)) 155 | 156 | self.set_limits(ax='ax_x%d'%i, xlim=xlim, ylim=ylim) 157 | self.set_label(ax='ax_x%d'%i, label=label) 158 | 159 | # set axis limits and labels of input curves 160 | for i, idxlabel in enumerate(self.plotinputs): 161 | idx, label = idxlabel 162 | 163 | try: 164 | ylim = (min(self.ut[:,idx]), max(self.ut[:,idx])) 165 | except: 166 | ylim = (min(self.ut), max(self.ut)) 167 | 168 | self.set_limits(ax='ax_u%d'%i, xlim=xlim, ylim=ylim) 169 | self.set_label(ax='ax_u%d'%i, label=label) 170 | 171 | # enable LaTeX text rendering --> slow 172 | plt.rc('text', usetex=True) 173 | 174 | class Image(): 175 | ''' 176 | This is just a container for the drawn system. 177 | ''' 178 | def __init__(self): 179 | self.patches = [] 180 | self.lines = [] 181 | 182 | def reset(self): 183 | self.patches = [] 184 | self.lines = [] 185 | 186 | def get_axes(self): 187 | sys = self.plotsys 188 | inputs = self.plotinputs 189 | 190 | if not sys+inputs: 191 | gs = GridSpec(1,1) 192 | else: 193 | l = len(sys+inputs) 194 | 195 | gs = GridSpec(l, 3) 196 | 197 | axes = dict() 198 | syscurves = [] 199 | inputcurves = [] 200 | 201 | if not sys+inputs: 202 | axes['ax_img'] = self.fig.add_subplot(gs[:,:]) 203 | else: 204 | axes['ax_img'] = self.fig.add_subplot(gs[:,1:]) 205 | 206 | for i in xrange(len(sys)): 207 | axes['ax_x%d'%i] = self.fig.add_subplot(gs[i,0]) 208 | 209 | curve = mpl.lines.Line2D([], [], color='black') 210 | syscurves.append(curve) 211 | 212 | axes['ax_x%d'%i].add_line(curve) 213 | 214 | lensys = len(sys) 215 | for i in xrange(len(inputs)): 216 | axes['ax_u%d'%i] = self.fig.add_subplot(gs[lensys+i,0]) 217 | 218 | curve = mpl.lines.Line2D([], [], color='black') 219 | inputcurves.append(curve) 220 | 221 | axes['ax_u%d'%i].add_line(curve) 222 | 223 | self.axes = axes 224 | self.syscurves = syscurves 225 | self.inputcurves = inputcurves 226 | 227 | def set_limits(self, ax='ax_img', xlim=(0,1), ylim=(0,1)): 228 | self.axes[ax].set_xlim(*xlim) 229 | self.axes[ax].set_ylim(*ylim) 230 | 231 | def set_label(self, ax='ax_img', label=''): 232 | self.axes[ax].set_ylabel(label, rotation='horizontal', horizontalalignment='right') 233 | 234 | def show(self, t=0.0, xlim=None, ylim=None): 235 | ''' 236 | Plots one frame of the system animation. 237 | 238 | Parameters 239 | ---------- 240 | 241 | t : float 242 | The time for which to plot the system 243 | ''' 244 | 245 | # determine index of sim_data values correponding to given time 246 | if t <= self.t[0]: 247 | i = 0 248 | elif t >= self.t[-1]: 249 | i = -1 250 | else: 251 | i = 0 252 | while self.t[i] < t: 253 | i += 1 254 | 255 | # draw picture 256 | image = self.image 257 | ax_img = self.axes['ax_img'] 258 | 259 | if image == 0: 260 | # init 261 | image = self.Image() 262 | else: 263 | # update 264 | for p in image.patches: 265 | p.remove() 266 | for l in image.lines: 267 | l.remove() 268 | image.reset() 269 | 270 | image = self.draw(self.xt[i,:], image=image) 271 | 272 | for p in image.patches: 273 | ax_img.add_patch(p) 274 | 275 | for l in image.lines: 276 | ax_img.add_line(l) 277 | 278 | self.image = image 279 | self.axes['ax_img'] = ax_img 280 | 281 | if xlim is not None and ylim is not None: 282 | self.set_limits(ax='ax_img', xlim=xlim, ylim=ylim) 283 | 284 | # update system curves 285 | for k, curve in enumerate(self.syscurves): 286 | try: 287 | curve.set_data(self.t[:i], self.xt[:i, self.plotsys[k][0]]) 288 | except: 289 | curve.set_data(self.t[:i], self.xt[:i]) 290 | self.axes['ax_x%d'%k].add_line(curve) 291 | 292 | # update input curves 293 | for k, curve in enumerate(self.inputcurves): 294 | try: 295 | curve.set_data(self.t[:i], self.ut[:i,self.plotinputs[k][0]]) 296 | except: 297 | curve.set_data(self.t[:i], self.ut[:i]) 298 | self.axes['ax_u%d'%k].add_line(curve) 299 | 300 | plt.draw() 301 | plt.show() 302 | 303 | def animate(self): 304 | ''' 305 | Starts the animation of the system. 306 | ''' 307 | t = self.t 308 | xt = self.xt 309 | ut = self.ut 310 | 311 | # NEW: try to repeat first and last frame 312 | pause_time = 1.0 #[s] 313 | 314 | # how many frames will be plotted per second of system time 315 | fps = self.nframes/(t[-1] - t[0]) 316 | 317 | # add so many frames that they fill the `pause` 318 | add_frames = int(fps * pause_time) 319 | 320 | for i in xrange(add_frames): 321 | t = np.hstack((t[0],t,t[-1])) 322 | xt = np.vstack((xt[0],xt,xt[-1])) 323 | ut = np.vstack((ut[0],ut,ut[-1])) 324 | 325 | 326 | #tt = np.linspace(0,len(t)-1,self.nframes,endpoint=True) 327 | tt = np.linspace(0,xt.shape[0]-1,self.nframes,endpoint=True) 328 | tt = np.hstack(([tt[0]]*add_frames,tt,[tt[-1]]*add_frames)) 329 | 330 | self.T = t[-1] - t[0] + 2 * pause_time 331 | 332 | # raise number of frames 333 | self.nframes += 2 * add_frames 334 | 335 | #IPS() 336 | 337 | 338 | def _animate(frame): 339 | i = tt[frame] 340 | print "frame = {f}, t = {t}, x = {x}, u = {u}".format(f=frame, t=t[i], x=xt[i,:], u=ut[i,:]) 341 | 342 | # draw picture 343 | image = self.image 344 | ax_img = self.axes['ax_img'] 345 | 346 | if image == 0: 347 | # init 348 | image = self.Image() 349 | else: 350 | # update 351 | for p in image.patches: 352 | p.remove() 353 | for l in image.lines: 354 | l.remove() 355 | image.reset() 356 | 357 | image = self.draw(xt[i,:], image=image) 358 | 359 | for p in image.patches: 360 | ax_img.add_patch(p) 361 | 362 | for l in image.lines: 363 | ax_img.add_line(l) 364 | 365 | self.image = image 366 | self.axes['ax_img'] = ax_img 367 | 368 | # update system curves 369 | for k, curve in enumerate(self.syscurves): 370 | try: 371 | curve.set_data(t[:i], xt[:i, self.plotsys[k][0]]) 372 | except: 373 | curve.set_data(t[:i], xt[:i]) 374 | self.axes['ax_x%d'%k].add_line(curve) 375 | 376 | # update input curves 377 | for k, curve in enumerate(self.inputcurves): 378 | try: 379 | curve.set_data(t[:i], ut[:i,self.plotinputs[k][0]]) 380 | except: 381 | curve.set_data(t[:i], ut[:i]) 382 | self.axes['ax_u%d'%k].add_line(curve) 383 | 384 | plt.draw() 385 | 386 | self.anim = animation.FuncAnimation(self.fig, _animate, frames=self.nframes, 387 | interval=1, blit=False) 388 | 389 | 390 | def save(self, fname, fps=None, dpi=200): 391 | ''' 392 | Saves the animation as a video file or animated gif. 393 | ''' 394 | if not fps: 395 | fps = self.nframes/(float(self.T)) # add pause_time here? 396 | 397 | if fname.endswith('gif'): 398 | self.anim.save(fname, writer='imagemagick', fps=fps) 399 | else: 400 | FFWriter = animation.FFMpegFileWriter() 401 | self.anim.save(fname, fps=fps, dpi=dpi, writer='mencoder') 402 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This file is part of PyTrajectory. 3 | ''' 4 | 5 | from distutils.core import setup 6 | 7 | setup(name='PyTrajectory', 8 | version='1.2.0', 9 | packages=['pytrajectory'], 10 | requires=['numpy (>=1.8.1)', 11 | 'sympy (>=0.7.5)', 12 | 'scipy (>=0.13.0)', 13 | 'matplotlib (<1.5.0)'], 14 | 15 | # metadata 16 | author='Andreas Kunze, Carsten Knoll, Oliver Schnabel', 17 | author_email='Andreas.Kunze@mailbox.tu-dresden.de', 18 | url='https://github.com/TUD-RST/pytrajectory', 19 | description='Python library for trajectory planning.', 20 | long_description=''' 21 | PyTrajectory is a Python library for the determination of the feed forward 22 | control to achieve a transition between desired states of a nonlinear control system. 23 | ''', 24 | #setup_requires=['numpy>=1.8.1', 'sympy>=0.7.5', 'scipy>=0.13.0', 'matplotlib<1.5.0'], 25 | #install_requires=['numpy>=1.8.1', 'sympy>=0.7.5', 'scipy>=0.13.0', 'matplotlib<1.5.0'] 26 | ) 27 | -------------------------------------------------------------------------------- /test/conftest.py: -------------------------------------------------------------------------------- 1 | # set up pytest to skip slow running tests by default 2 | 3 | import pytest 4 | 5 | def pytest_addoption(parser): 6 | parser.addoption("--runslow", action="store_true", 7 | help="run slow tests") 8 | 9 | def pytest_runtest_setup(item): 10 | if 'slow' in item.keywords and not item.config.getoption("--runslow"): 11 | pytest.skip("need --runslow option to run") -------------------------------------------------------------------------------- /test/test_auxiliary.py: -------------------------------------------------------------------------------- 1 | # IMPORTS 2 | 3 | import pytrajectory 4 | import pytest 5 | import sympy as sp 6 | import numpy as np 7 | 8 | 9 | class TestCseLambdify(object): 10 | 11 | def test_single_expression(self): 12 | x, y = sp.symbols('x, y') 13 | 14 | e = 0.5*(x + y) + sp.asin(sp.sin(0.5*(x+y))) + sp.sin(x+y)**2 + sp.cos(x+y)**2 15 | 16 | f = pytrajectory.auxiliary.cse_lambdify(args=(x,y), expr=e, modules='numpy') 17 | 18 | assert f(1., 1.) == 3. 19 | 20 | def test_list(self): 21 | x, y = sp.symbols('x, y') 22 | ones = np.ones(10) 23 | 24 | l = [0.5*(x + y), sp.asin(sp.sin(0.5*(x+y))), sp.sin(x+y)**2 + sp.cos(x+y)**2] 25 | 26 | f = pytrajectory.auxiliary.cse_lambdify(args=(x,y), expr=l, modules='numpy') 27 | 28 | assert f(1., 1.) == [1., 1., 1.] 29 | for i in f(ones, ones): 30 | assert np.allclose(i, ones) 31 | 32 | def test_matrix_to_matrix(self): 33 | x, y = sp.symbols('x, y') 34 | ones = np.ones(10) 35 | 36 | M = sp.Matrix([0.5*(x + y), sp.asin(sp.sin(0.5*(x+y))), sp.sin(x+y)**2 + sp.cos(x+y)**2]) 37 | 38 | f = pytrajectory.auxiliary.cse_lambdify(args=(x,y), expr=M, 39 | modules='numpy') 40 | 41 | assert type(f(1., 1.)) == np.matrix 42 | assert np.allclose(f(1. ,1.), np.ones((3,1))) 43 | 44 | def test_matrix_to_array(self): 45 | x, y = sp.symbols('x, y') 46 | ones = np.ones(10) 47 | 48 | M = sp.Matrix([0.5*(x + y), sp.asin(sp.sin(0.5*(x+y))), sp.sin(x+y)**2 + sp.cos(x+y)**2]) 49 | 50 | f = pytrajectory.auxiliary.cse_lambdify(args=(x,y), expr=M, 51 | modules=[{'ImmutableMatrix' : np.array}, 'numpy']) 52 | 53 | F = f(1., 1.) 54 | 55 | assert type(F == np.ndarray) 56 | assert not isinstance(F, np.matrix) 57 | assert F.shape == (3,1) 58 | assert np.allclose(F, np.ones((3,1))) 59 | 60 | #@pytest.xfail(reason="Not implemented, yet") 61 | #def test_1d_array_input(self): 62 | # x, y = sp.symbols('x, y') 63 | # 64 | # A = np.array([0.5*(x + y), sp.asin(sp.sin(0.5*(x+y))), sp.sin(x+y)**2 + sp.cos(x+y)**2]) 65 | # 66 | # f = pytrajectory.auxiliary.cse_lambdify(args=(x,y), expr=A, 67 | # modules=[{'ImmutableMatrix' : np.array}, 'numpy']) 68 | # 69 | # F = f(1., 1.) 70 | # 71 | # assert type(F) == np.ndarray 72 | # assert F.shape == (3,) 73 | # assert F == np.ones(3) 74 | 75 | def test_lambdify_returns_numpy_array_with_dummify_true(self): 76 | x, y = sp.symbols('x, y') 77 | 78 | M = sp.Matrix([[x], 79 | [y]]) 80 | 81 | f_arr = sp.lambdify(args=(x,y), expr=M, dummify=True, modules=[{'ImmutableMatrix' : np.array}, 'numpy']) 82 | 83 | assert isinstance(f_arr(1,1), np.ndarray) 84 | assert not isinstance(f_arr(1,1), np.matrix) 85 | 86 | # following test is not relevant for pytrajectory 87 | # but might be for an outsourcing of the cse_lambdify function 88 | @pytest.mark.xfail(reason='..') 89 | def test_lambdify_returns_numpy_array_with_dummify_false(self): 90 | x, y = sp.symbols('x, y') 91 | 92 | M = sp.Matrix([[x], 93 | [y]]) 94 | 95 | f_arr = sp.lambdify(args=(x,y), expr=M, dummify=False, modules=[{'ImmutableMatrix' : np.array}, 'numpy']) 96 | 97 | assert isinstance(f_arr(1,1), np.ndarray) 98 | assert not isinstance(f_arr(1,1), np.matrix) 99 | 100 | def test_orig_args_in_reduced_expr(self): 101 | x, y = sp.symbols('x, y') 102 | 103 | expr = (x + y)**2 + sp.cos(x + y) + x 104 | 105 | f = pytrajectory.auxiliary.cse_lambdify(args=(x, y), expr=expr, modules='numpy') 106 | 107 | assert f(0., 0.) == 1. 108 | -------------------------------------------------------------------------------- /test/test_examples.py: -------------------------------------------------------------------------------- 1 | # IMPORTS 2 | 3 | import os 4 | import sys 5 | import inspect 6 | import pytest 7 | 8 | import pytrajectory 9 | 10 | 11 | class TestExamples(object): 12 | # first, we need to get the path to the example scripts 13 | # 14 | # so we take the directory name of the absolute path 15 | # of the source or compiled file in which the top of the 16 | # call stack was defined in 17 | # (should be this file...!) 18 | pth = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) 19 | 20 | # the example scripts are located in a directory one level above the test scripts 21 | # so we remove the last directory in the path 22 | pth = pth.split(os.sep)[:-1] 23 | 24 | # and add that of the example 25 | examples_dir = os.sep.join(pth + ['examples']) 26 | 27 | # now we test, if we can get the example scripts 28 | test_example_path_failed = True 29 | with open(os.path.join(examples_dir, 'ex0_InvertedPendulumSwingUp.py')) as f: 30 | f.close() 31 | test_example_path_failed = False 32 | 33 | def assert_reached_accuracy(self, loc): 34 | for value in loc.values(): 35 | if isinstance(value, pytrajectory.system.ControlSystem): 36 | assert value.reached_accuracy 37 | 38 | @pytest.mark.skipif(test_example_path_failed, reason="Cannot get example scripts!") 39 | def test_inverted_pendulum_swing_up(self): 40 | script = os.path.join(self.examples_dir, 'ex0_InvertedPendulumSwingUp.py') 41 | d = dict(locals(), **globals()) 42 | execfile(script, d, d) 43 | self.assert_reached_accuracy(locals()) 44 | 45 | @pytest.mark.skipif(test_example_path_failed, reason="Cannot get example scripts!") 46 | def test_inverted_pendulum_translation(self): 47 | script = os.path.join(self.examples_dir, 'ex1_InvertedPendulumTranslation.py') 48 | d = dict(locals(), **globals()) 49 | execfile(script, d, d) 50 | self.assert_reached_accuracy(locals()) 51 | 52 | @pytest.mark.skipif(test_example_path_failed, reason="Cannot get example scripts!") 53 | def test_inverted_dual_pendulum_swing_up(self): 54 | script = os.path.join(self.examples_dir, 'ex2_InvertedDualPendulumSwingUp.py') 55 | d = dict(locals(), **globals()) 56 | execfile(script, d, d) 57 | self.assert_reached_accuracy(locals()) 58 | 59 | @pytest.mark.skipif(test_example_path_failed, reason="Cannot get example scripts!") 60 | def test_aricraft(self): 61 | script = os.path.join(self.examples_dir, 'ex3_Aircraft.py') 62 | d = dict(locals(), **globals()) 63 | execfile(script, d, d) 64 | self.assert_reached_accuracy(locals()) 65 | 66 | @pytest.mark.skipif(test_example_path_failed, reason="Cannot get example scripts!") 67 | def test_underactuated_manipulator(self): 68 | script = os.path.join(self.examples_dir, 'ex4_UnderactuatedManipulator.py') 69 | d = dict(locals(), **globals()) 70 | execfile(script, d, d) 71 | self.assert_reached_accuracy(locals()) 72 | 73 | @pytest.mark.skipif(test_example_path_failed, reason="Cannot get example scripts!") 74 | def test_acrobot(self): 75 | script = os.path.join(self.examples_dir, 'ex5_Acrobot.py') 76 | d = dict(locals(), **globals()) 77 | execfile(script, d, d) 78 | self.assert_reached_accuracy(locals()) 79 | 80 | @pytest.mark.skipif(test_example_path_failed, reason="Cannot get example scripts!") 81 | def test_constrained_double_integrator(self): 82 | script = os.path.join(self.examples_dir, 'ex6_ConstrainedDoubleIntegrator.py') 83 | d = dict(locals(), **globals()) 84 | execfile(script, d, d) 85 | self.assert_reached_accuracy(locals()) 86 | 87 | @pytest.mark.skipif(test_example_path_failed, reason="Cannot get example scripts!") 88 | def test_constrained_inverted_pendulum(self): 89 | script = os.path.join(self.examples_dir, 'ex7_ConstrainedInvertedPendulum.py') 90 | d = dict(locals(), **globals()) 91 | execfile(script, d, d) 92 | self.assert_reached_accuracy(locals()) 93 | 94 | @pytest.mark.slow 95 | @pytest.mark.skipif(test_example_path_failed, reason="Cannot get example scripts!") 96 | def test_constrained_double_pendulum(self): 97 | script = os.path.join(self.examples_dir, 'ex8_ConstrainedDoublePendulum.py') 98 | d = dict(locals(), **globals()) 99 | execfile(script, d, d) 100 | self.assert_reached_accuracy(locals()) 101 | --------------------------------------------------------------------------------