From 09a715377a868a1f46b7266bc8af58b932d1999e Mon Sep 17 00:00:00 2001 From: Clancy Rowley Date: Thu, 2 Apr 2015 08:41:14 -0400 Subject: [PATCH 1/6] Simplify setup.py and automatic version generation There was a lot of unnecessarily complex logic in setup.py, taken from numpy, about whether to use setuptools or distutils. In addition, the procedure for generating the version information was only partially automatic (one still needed to change the version manually in setup.py), and was somewhat unreliable (see issue #37). This commit simplifies setup.py, always using setuptools, as recommended in the Python Packaging Guide: http://python-packaging-user-guide.readthedocs.org/en/latest/current.html The version information is now generated directly from tags in the git repository. Now, *before* running setup.py, one runs python make_version.py and this generates a file with the version information. This is copied from binstar (https://github.com/Binstar/binstar) and seems to work well. --- .gitignore | 2 +- .travis.yml | 1 + control/__init__.py | 103 +++++++++----------- make_version.py | 27 ++++++ setup.py | 226 +++++++------------------------------------- 5 files changed, 109 insertions(+), 250 deletions(-) create mode 100644 make_version.py diff --git a/.gitignore b/.gitignore index 7c490a4ea..48b18d93c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ build/ dist/ .ropeproject/ MANIFEST -control/version.py +control/_version.py build.log *.egg-info/ .coverage diff --git a/.travis.yml b/.travis.yml index 49fb7bee7..f8ef0e975 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,6 +22,7 @@ before_install: - conda config --set always_yes yes --set changeps1 no - conda update -q conda - conda info -a + - python make_version.py # Install packages install: 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/make_version.py b/make_version.py new file mode 100644 index 000000000..e451bfae7 --- /dev/null +++ b/make_version.py @@ -0,0 +1,27 @@ +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)) + + +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', +) From 7f937266aec38f13ca77b9cb0b436d09f752a515 Mon Sep 17 00:00:00 2001 From: Clancy Rowley Date: Thu, 2 Apr 2015 15:09:21 -0400 Subject: [PATCH 2/6] Update and improve README --- README.rst | 111 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 71 insertions(+), 40 deletions(-) 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 From 688d1f6feae72c396f101af08b85b5f94c992a6f Mon Sep 17 00:00:00 2001 From: Clancy Rowley Date: Thu, 2 Apr 2015 18:11:07 -0400 Subject: [PATCH 3/6] Fix print statement in tests/freqresp.py for py3 --- control/tests/freqresp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 6d337541fa6e48753105295d59f1b9abc2cc36d0 Mon Sep 17 00:00:00 2001 From: Clancy Rowley Date: Thu, 2 Apr 2015 17:27:47 -0400 Subject: [PATCH 4/6] Add conda recipe To build the conda package: conda build conda-recipe Note that the version is automatically set according to the files generated by make_version.py, which is automatically run by the recipe --- .gitignore | 2 ++ conda-recipe/bld.bat | 3 +++ conda-recipe/meta.yaml | 34 ++++++++++++++++++++++++++++++++++ make_version.py | 13 +++++++++++++ 4 files changed, 52 insertions(+) create mode 100644 conda-recipe/bld.bat create mode 100644 conda-recipe/meta.yaml diff --git a/.gitignore b/.gitignore index 48b18d93c..79445c5e5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ dist/ .ropeproject/ MANIFEST control/_version.py +__conda_*.txt +record.txt build.log *.egg-info/ .coverage 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..59d899734 --- /dev/null +++ b/conda-recipe/meta.yaml @@ -0,0 +1,34 @@ +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 + + run: + - python + - numpy + - scipy + - matplotlib + +test: + requires: + - nose + + 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/make_version.py b/make_version.py index e451bfae7..87bbe8b51 100644 --- a/make_version.py +++ b/make_version.py @@ -22,6 +22,19 @@ def main(): 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() From 4a35d468f16f367fdc6c54ce555682f6a576d846 Mon Sep 17 00:00:00 2001 From: Clancy Rowley Date: Thu, 2 Apr 2015 17:34:08 -0400 Subject: [PATCH 5/6] Modify travis build to test conda recipe --- .travis.yml | 5 +++-- conda-recipe/meta.yaml | 4 +--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index f8ef0e975..d2603ce28 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,12 +21,13 @@ 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 info -a - - python make_version.py # 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 - pip install coveralls - pip install slycot diff --git a/conda-recipe/meta.yaml b/conda-recipe/meta.yaml index 59d899734..7a578191f 100644 --- a/conda-recipe/meta.yaml +++ b/conda-recipe/meta.yaml @@ -10,6 +10,7 @@ build: requirements: build: - python + - nose run: - python @@ -18,9 +19,6 @@ requirements: - matplotlib test: - requires: - - nose - imports: - control From 5ff196c860909d18707ccdd2f85a89a4b531150c Mon Sep 17 00:00:00 2001 From: Clancy Rowley Date: Thu, 2 Apr 2015 22:36:13 -0400 Subject: [PATCH 6/6] Install slycot from binstar in Travis CI The majority of time in the Travis CI testing was spent installing slycot from source. (This also required installing a fortran compiler as part of the Travis build.) Installing from a binary saves a lot of time. --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index d2603ce28..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; @@ -22,14 +20,15 @@ before_install: - 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 build conda-recipe - conda install control --use-local + - conda install slycot - pip install coveralls - - pip install slycot # command to run tests script: