diff --git a/.gitignore b/.gitignore index 7c490a4ea..79445c5e5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,9 @@ build/ dist/ .ropeproject/ MANIFEST -control/version.py +control/_version.py +__conda_*.txt +record.txt build.log *.egg-info/ .coverage diff --git a/.travis.yml b/.travis.yml index 49fb7bee7..0289d0594 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,8 +8,6 @@ python: before_install: - export DISPLAY=:99.0 - sh -e /etc/init.d/xvfb start - - sudo apt-get update --fix-missing -qq - - sudo apt-get install gfortran liblapack-dev # use miniconda to install numpy/scipy, to avoid lengthy build from source - if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then wget http://repo.continuum.io/miniconda/Miniconda-3.4.2-Linux-x86_64.sh -O miniconda.sh; @@ -21,13 +19,16 @@ before_install: - hash -r - conda config --set always_yes yes --set changeps1 no - conda update -q conda + - conda install --yes python=$TRAVIS_PYTHON_VERSION conda-build pip coverage + - conda config --add channels http://conda.binstar.org/cwrowley - conda info -a # Install packages install: - - conda install --yes python=$TRAVIS_PYTHON_VERSION coverage nose numpy scipy matplotlib pip + - conda build conda-recipe + - conda install control --use-local + - conda install slycot - pip install coveralls - - pip install slycot # command to run tests script: diff --git a/README.rst b/README.rst index 95e3e2649..79b48c517 100644 --- a/README.rst +++ b/README.rst @@ -1,65 +1,96 @@ -Python Control System Library -============================= - .. image:: https://travis-ci.org/python-control/python-control.svg?branch=master - :target: https://travis-ci.org/python-control/python-control + :target: https://travis-ci.org/python-control/python-control .. image:: https://coveralls.io/repos/python-control/python-control/badge.png - :target: https://coveralls.io/r/python-control/python-control + :target: https://coveralls.io/r/python-control/python-control + +Python Control Systems Library +============================== + +The Python Control Systems Library is a Python module that implements basic +operations for analysis and design of feedback control systems. + +Features +-------- + +- Linear input/output systems in state-space and frequency domain +- Block diagram algebra: serial, parallel, and feedback interconnections +- Time response: initial, step, impulse +- Frequency response: Bode and Nyquist plots +- Control analysis: stability, reachability, observability, stability margins +- Control design: eigenvalue placement, linear quadratic regulator +- Estimator design: linear quadratic estimator (Kalman filter) + + +Links +===== + +- Project home page: http://python-control.sourceforge.net +- Source code repository: https://github.com/python-control/python-control +- Documentation: http://python-control.readthedocs.org/ +- Issue tracker: https://github.com/python-control/python-control/issues +- Mailing list: http://sourceforge.net/p/python-control/mailman/ + + +Dependencies +============ -RMM, 23 May 09 +The package requires numpy, scipy, and matplotlib. In addition, some routines +use a module called slycot, that is a Python wrapper around some FORTRAN +routines. Many parts of python-control will work without slycot, but some +functionality is limited or absent, and installation of slycot is recommended +(see below). Note that in order to install slycot, you will need a FORTRAN +compiler on your machine. The Slycot wrapper can be found at: -This directory contains the source code for the Python Control Systems -Library (python-control). This package provides a library of standard -control system algorithms in the python programming environment. +https://github.com/jgoppert/Slycot Installation ------------- +============ + +The package may be installed using pip or distutils. + +Pip +--- + +To install using pip:: -Using pip -~~~~~~~~~~~ + pip install slycot # optional + pip install control -Pip is a python packaging system. It can be installed on debian based -linux distros with the command:: +Distutils +--------- - sudo apt-get install pip +To install in your home directory, use:: -Pip can then be used to install python-control:: + python setup.py install --user - sudo pip install control +To install for all users (on Linux or Mac OS):: + python setup.py build + sudo python setup.py install -From Source -~~~~~~~~~~~ -Standard python package installation:: +Development +=========== - python setup.py install +Code +---- -To see if things are working, you can run the script -examples/secord-matlab.py (using ipython -pylab). It should generate a step -response, Bode plot and Nyquist plot for a simple second order linear -system. +You can check out the latest version of the source code with the command:: + + git clone https://github.com/python-control/python-control.git Testing ------- -You can also run a set of unit tests to make sure that everything is working +You can run a set of unit tests to make sure that everything is working correctly. After installation, run:: - python runtests.py - -Slycot ------- - -Routines from the Slycot wrapper are used for providing the -functionality of several routines for state-space, transfer functions -and robust control. Many parts of python-control will still work -without slycot, but some functionality is limited or absent, and -installation of Slycot is definitely recommended. The Slycot wrapper -can be found at: + python setup.py test -https://github.com/jgoppert/Slycot +Contributing +------------ -and can be installed with:: +Your contributions are welcome! Simply fork the GitHub repository and send a +`pull request`_. - sudo pip install slycot +.. _pull request: https://github.com/python-control/python-control/pulls diff --git a/conda-recipe/bld.bat b/conda-recipe/bld.bat new file mode 100644 index 000000000..11163e37d --- /dev/null +++ b/conda-recipe/bld.bat @@ -0,0 +1,3 @@ +cd %RECIPE_DIR%\.. +%PYTHON% make_version.py +%PYTHON% setup.py install --single-version-externally-managed --record=record.txt diff --git a/conda-recipe/meta.yaml b/conda-recipe/meta.yaml new file mode 100644 index 000000000..7a578191f --- /dev/null +++ b/conda-recipe/meta.yaml @@ -0,0 +1,32 @@ +package: + name: control + +build: + script: + - cd $RECIPE_DIR/.. + - $PYTHON make_version.py + - $PYTHON setup.py install --single-version-externally-managed --record=record.txt + +requirements: + build: + - python + - nose + + run: + - python + - numpy + - scipy + - matplotlib + +test: + imports: + - control + +about: + home: http://python-control.sourceforge.net + license: BSD License + summary: 'Python control systems library' + +# See +# http://docs.continuum.io/conda/build.html for +# more information about meta.yaml diff --git a/control/__init__.py b/control/__init__.py index 12d3b15e7..f2e16f455 100644 --- a/control/__init__.py +++ b/control/__init__.py @@ -56,65 +56,56 @@ lqe linear quadratic estimator """ -try: - __CONTROL_SETUP__ -except NameError: - __CONTROL_SETUP__ = False - -if __CONTROL_SETUP__: - import sys as _sys - _sys.stderr.write('Running from control source directory.\n') - del _sys -else: +# Import functions from within the control system library +# Should probably only import the exact functions we use... +from .bdalg import series, parallel, negate, feedback +from .delay import pade +from .dtime import sample_system +from .freqplot import bode_plot, nyquist_plot, gangof4_plot +from .freqplot import bode, nyquist, gangof4 +from .lti import issiso, timebase, timebaseEqual, isdtime, isctime +from .margins import stability_margins, phase_crossover_frequencies +from .mateqn import lyap, dlyap, care, dare +from .modelsimp import hsvd, modred, balred, era, markov +from .nichols import nichols_plot, nichols +from .phaseplot import phase_plot, box_grid +from .rlocus import root_locus +from .statefbk import place, lqr, ctrb, obsv, gram, acker +from .statesp import StateSpace +from .timeresp import forced_response, initial_response, step_response, \ + impulse_response +from .xferfcn import TransferFunction +from .ctrlutil import unwrap, issys +from .frdata import FRD +from .canonical import canonical_form, reachable_form - # Import functions from within the control system library - # Should probably only import the exact functions we use... - from .bdalg import series, parallel, negate, feedback - from .delay import pade - from .dtime import sample_system - from .freqplot import bode_plot, nyquist_plot, gangof4_plot - from .freqplot import bode, nyquist, gangof4 - from .lti import issiso, timebase, timebaseEqual, isdtime, isctime - from .margins import stability_margins, phase_crossover_frequencies - from .mateqn import lyap, dlyap, care, dare - from .modelsimp import hsvd, modred, balred, era, markov - from .nichols import nichols_plot, nichols - from .phaseplot import phase_plot, box_grid - from .rlocus import root_locus - from .statefbk import place, lqr, ctrb, obsv, gram, acker - from .statesp import StateSpace - from .timeresp import forced_response, initial_response, step_response, \ - impulse_response - from .xferfcn import TransferFunction - from .ctrlutil import unwrap, issys - from .frdata import FRD - from .canonical import canonical_form, reachable_form +# Exceptions +from .exception import * - # Exceptions - from .exception import * - - # Version information - from control.version import full_version as __version__ - from control.version import git_revision as __git_revision__ +# Version information +try: + from ._version import __version__, __commit__ +except ImportError: + __version__ = "dev" - # Import some of the more common (and benign) MATLAB shortcuts - # By default, don't import conflicting commands here - #! TODO (RMM, 4 Nov 2012): remove MATLAB dependencies from __init__.py - #! - #! Eventually, all functionality should be in modules *other* than matlab. - #! This will allow inclusion of the matlab module to set up a different set - #! of defaults from the main package. At that point, the matlab module will - #! allow provide compatibility with MATLAB but no package functionality. - #! - from .matlab import ss, tf, ss2tf, tf2ss, drss - from .matlab import pole, zero, evalfr, freqresp, dcgain - from .matlab import nichols, rlocus, margin - # bode and nyquist come directly from freqplot.py - from .matlab import step, impulse, initial, lsim - from .matlab import ssdata, tfdata +# Import some of the more common (and benign) MATLAB shortcuts +# By default, don't import conflicting commands here +#! TODO (RMM, 4 Nov 2012): remove MATLAB dependencies from __init__.py +#! +#! Eventually, all functionality should be in modules *other* than matlab. +#! This will allow inclusion of the matlab module to set up a different set +#! of defaults from the main package. At that point, the matlab module will +#! allow provide compatibility with MATLAB but no package functionality. +#! +from .matlab import ss, tf, ss2tf, tf2ss, drss +from .matlab import pole, zero, evalfr, freqresp, dcgain +from .matlab import nichols, rlocus, margin + # bode and nyquist come directly from freqplot.py +from .matlab import step, impulse, initial, lsim +from .matlab import ssdata, tfdata # The following is to use Numpy's testing framework # Tests go under directory tests/, benchmarks under directory benchmarks/ - from numpy.testing import Tester - test = Tester().test - bench = Tester().bench +from numpy.testing import Tester +test = Tester().test +bench = Tester().bench diff --git a/control/tests/freqresp.py b/control/tests/freqresp.py index eb91ea487..88a6d4755 100644 --- a/control/tests/freqresp.py +++ b/control/tests/freqresp.py @@ -40,7 +40,7 @@ systf = tf(sys) tfMIMO = tf(sysMIMO) -print systf.pole() +print(systf.pole()) #print tfMIMO.pole() # - should throw not implemented exception #print tfMIMO.zero() # - should throw not implemented exception diff --git a/make_version.py b/make_version.py new file mode 100644 index 000000000..87bbe8b51 --- /dev/null +++ b/make_version.py @@ -0,0 +1,40 @@ +from subprocess import check_output +import os + +def main(): + cmd = 'git describe --always --long' + output = check_output(cmd.split()).decode('utf-8').strip().split('-') + if len(output) == 3: + version, build, commit = output + else: + raise Exception("Could not git describe, (got %s)" % output) + + print("Version: %s" % version) + print("Build: %s" % build) + print("Commit: %s\n" % commit) + + filename = "control/_version.py" + print("Writing %s" % filename) + with open(filename, 'w') as fd: + if build == '0': + fd.write('__version__ = "%s"\n' % (version)) + else: + fd.write('__version__ = "%s.post%s"\n' % (version, build)) + fd.write('__commit__ = "%s"\n' % (commit)) + + # Write files for conda version number + SRC_DIR = os.environ.get('SRC_DIR', '.') + conda_version_path = os.path.join(SRC_DIR, '__conda_version__.txt') + print("Writing %s" % conda_version_path) + with open(conda_version_path, 'w') as conda_version: + conda_version.write(version) + + conda_buildnum_path = os.path.join(SRC_DIR, '__conda_buildnum__.txt') + print("Writing %s" % conda_buildnum_path) + + with open(conda_buildnum_path, 'w') as conda_buildnum: + conda_buildnum.write(build) + + +if __name__ == '__main__': + main() diff --git a/setup.py b/setup.py index e7f53095d..3caf29427 100644 --- a/setup.py +++ b/setup.py @@ -1,55 +1,26 @@ -#!/usr/bin/env python -descr = """Python Control Systems Library +from setuptools import setup, find_packages -The Python Control Systems Library, python-control, -is a python module that implements basic operations -for analysis and design of feedback control systems. +ver = {} +try: + with open('control/_version.py') as fd: + exec(fd.read(), ver) + version = ver.get('__version__', 'dev') +except IOError: + version = 'dev' -Features: -Linear input/output systems in state space and frequency domain -Block diagram algebra: serial, parallel and feedback interconnections -Time response: initial, step, impulse -Frequency response: Bode and Nyquist plots -Control analysis: stability, reachability, observability, stability margins -Control design: eigenvalue placement, linear quadratic regulator -Estimator design: linear quadratic estimator (Kalman filter) +with open('README.rst') as fp: + long_description = fp.read() -""" - -MAJOR = 0 -MINOR = 6 -MICRO = 5 -ISRELEASED = True -DISTNAME = 'control' -DESCRIPTION = 'Python control systems library' -LONG_DESCRIPTION = descr -AUTHOR = 'Richard Murray' -AUTHOR_EMAIL = 'murray@cds.caltech.edu' -MAINTAINER = AUTHOR -MAINTAINER_EMAIL = AUTHOR_EMAIL -URL = 'http://python-control.sourceforge.net' -LICENSE = 'BSD' -DOWNLOAD_URL = URL -PACKAGE_NAME = 'control' -EXTRA_INFO = dict( - install_requires=['numpy', 'scipy', 'matplotlib'], - tests_require=['scipy', 'matplotlib', 'nose'] -) - -VERSION = '%d.%d.%d' % (MAJOR, MINOR, MICRO) - -import os -import sys -import subprocess - - -CLASSIFIERS = """\ +CLASSIFIERS = """ Development Status :: 3 - Alpha Intended Audience :: Science/Research Intended Audience :: Developers License :: OSI Approved :: BSD License -Programming Language :: Python +Programming Language :: Python :: 2 +Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 +Programming Language :: Python :: 3.3 +Programming Language :: Python :: 3.4 Topic :: Software Development Topic :: Scientific/Engineering Operating System :: Microsoft :: Windows @@ -58,152 +29,21 @@ Operating System :: MacOS """ - -# Return the git revision as a string -def git_version(): - def _minimal_ext_cmd(cmd): - # construct minimal environment - env = {} - for k in ['SYSTEMROOT', 'PATH']: - v = os.environ.get(k) - if v is not None: - env[k] = v - # LANGUAGE is used on win32 - env['LANGUAGE'] = 'C' - env['LANG'] = 'C' - env['LC_ALL'] = 'C' - out = subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - env=env).communicate()[0] - return out - - try: - out = _minimal_ext_cmd(['git', 'rev-parse', 'HEAD']) - GIT_REVISION = out.strip().decode('ascii') - except OSError: - GIT_REVISION = "Unknown" - - return GIT_REVISION - - -def get_version_info(): - # Adding the git rev number needs to be done inside write_version_py(), - # otherwise the import of package.version messes up - # the build under Python 3. - FULLVERSION = VERSION - if os.path.exists('.git'): - GIT_REVISION = git_version() - elif os.path.exists('control/version.py'): - # must be a source distribution, use existing version file - try: - from control.version import git_revision as GIT_REVISION - except ImportError: - raise ImportError("Unable to import git_revision. Try removing " - "control/version.py and the build directory " - "before building.") - else: - GIT_REVISION = "Unknown" - - if not ISRELEASED: - FULLVERSION += '.dev-' + GIT_REVISION[:7] - - return FULLVERSION, GIT_REVISION - - -def write_version_py(filename='control/version.py'): - cnt = """ -# THIS FILE IS GENERATED FROM SETUP.PY -short_version = '%(version)s' -version = '%(version)s' -full_version = '%(full_version)s' -git_revision = '%(git_revision)s' -release = %(isrelease)s - -if not release: - version = full_version -""" - FULLVERSION, GIT_REVISION = get_version_info() - - a = open(filename, 'w') - try: - a.write(cnt % {'version': VERSION, - 'full_version': FULLVERSION, - 'git_revision': GIT_REVISION, - 'isrelease': str(ISRELEASED)}) - finally: - a.close() - -def configuration(parent_package='',top_path=None): - from numpy.distutils.misc_util import Configuration - - config = Configuration(None, parent_package, top_path) - config.set_options(ignore_setup_xxx_py=True, - assume_default_configuration=True, - delegate_options_to_subpackages=True, - quiet=True) - - config.add_subpackage(PACKAGE_NAME) - - config.get_version(PACKAGE_NAME + '/version.py') # sets config.version - - return config - -def setup_package(): - src_path = os.path.dirname(os.path.abspath(sys.argv[0])) - old_path = os.getcwd() - os.chdir(src_path) - sys.path.insert(0, src_path) - - # Rewrite the version file everytime - write_version_py() - - metadata = dict( - name=DISTNAME, - author=AUTHOR, - author_email=AUTHOR_EMAIL, - maintainer=MAINTAINER, - maintainer_email=MAINTAINER_EMAIL, - description=DESCRIPTION, - license=LICENSE, - url=URL, - download_url=DOWNLOAD_URL, - long_description=LONG_DESCRIPTION, - classifiers=[_f for _f in CLASSIFIERS.split('\n') if _f], - platforms=["Windows", "Linux", "Solaris", "Mac OS-X", "Unix"], - install_requires=['numpy', 'scipy'], - tests_require=['nose'], - test_suite='nose.collector', - packages=[PACKAGE_NAME], - ) - - # Run build - if len(sys.argv) >= 2 and ('--help' in sys.argv[1:] or - sys.argv[1] in ('--help-commands', 'egg_info', '--version', - 'clean', 'test')): - # Use setuptools for these commands (they don't work well or at all - # with distutils). For normal builds use distutils. - try: - from setuptools import setup - except ImportError: - from distutils.core import setup - - FULLVERSION, GIT_REVISION = get_version_info() - metadata['version'] = FULLVERSION - else: - if len(sys.argv) >= 2 and sys.argv[1] == 'bdist_wheel': - # bdist_wheel needs setuptools - import setuptools - from numpy.distutils.core import setup - cwd = os.path.abspath(os.path.dirname(__file__)) - metadata['configuration'] = configuration - - try: - setup(**metadata) - finally: - del sys.path[0] - os.chdir(old_path) - return - -if __name__ == '__main__': - setup_package() +setup( + name='control', + version=version, + author='Richard Murray', + author_email='murray@cds.caltech.edu', + url='http://python-control.sourceforge.net', + description='Python control systems library', + long_description=long_description, + packages=find_packages(), + classifiers=[f for f in CLASSIFIERS.split('\n') if f], + install_requires=['numpy', + 'scipy', + 'matplotlib'], + tests_require=['scipy', + 'matplotlib', + 'nose'], + test_suite = 'nose.collector', +)