Library / SDK
python-control/python-control avatar
python-control/python-control

python-control: state space models, interconnection algebra and the slycot gate

The Python Control Systems Library is a Python module that implements basic operations for analysis and design of feedback control systems.

2,111 stars470 forksPythonBSD-3-Clause

At a glance

What is it?
python-control is a Python module for analysis and design of feedback control systems, wrapping state space objects, block diagram algebra and frequency domain plots into one namespace. An optional slycot layer decides which design routines exist in a given install.
Who is it for?
python-control suits control coursework, notebook work and offline plant analysis, where slycot installs easily and the flat `control` namespace keeps scripts short. It is a poor fit for embedded loops, hardware in the loop work or code generation, since `pyproject.toml` marks the package Development Status :: 4 - Beta and sets a Python floor of 3.10.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 24 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

State space objects and the interconnection algebra that joins them

The PyPI name of this project is `control`, and its declared purpose is basic operations for the analysis and design of feedback control systems. Its object model is deliberately small. A plant or a controller is a linear input/output system expressed either in state-space form or in the frequency domain, and nearly every other capability is an operation that hands back another system object rather than a bare matrix.

The layer that earns the library its place is block diagram algebra. Serial, parallel and feedback compositions, plus the other interconnections listed beside them, build a combined system directly, so the result can be stepped, plotted or linearised like any object you constructed yourself. In teaching and in quick plant studies that matters, because hand-assembling transfer function matrices is where most scratch scripts quietly go wrong.

Analysis routines cover stability, reachability, observability, stability margins and root locus. Design routines cover eigenvalue placement, the linear quadratic regulator, `sisotool`, `hinfsyn` and `rootlocus_pid_designer`, and estimator design covers the linear quadratic estimator, the Kalman filter for linear systems. Nonlinear work is a separate front door: optimization-based control, describing functions and differential flatness. Read the package metadata before committing, though. `pyproject.toml` declares Development Status :: 4 - Beta, an honest signal about where a plant study should stop and a dedicated toolset should take over.

Response families rest on three hard dependencies, not an extra

Response analysis is organised into two named families. Time response covers initial, step and impulse. Frequency response covers Bode, Nyquist and Nichols plots. Plotting is not an optional extra here: `numpy>=1.23`, `scipy>=1.8` and `matplotlib>=3.6` sit in the declared dependencies of the package.

yaml
dependencies = [
    "numpy>=1.23",
    "scipy>=1.8",
    "matplotlib>=3.6",
]

Because matplotlib is mandatory, a plot call fails for numerical reasons rather than because a backend was left out, which removes a class of setup friction that optional plotting layers create.

The Python floor is explicit as well. The requirement is set to 3.10 or newer, and the classifiers name Python 3.10, 3.11 and 3.12 alongside Windows, POSIX, Unix and MacOS. On anything older the install fails outright instead of degrading.

Detail beyond the README lives in three other places: the project page at python-control.org, rendered documentation at python-control.readthedocs.io, and a mailing list hosted on sourceforge.net. What the README does not document is any headless or non-interactive rendering switch, so a batch job has to settle matplotlib's backend outside this package.

Design routines split by what the solver underneath can do

Design is where the dependency question bites. Six entry points are named: eigenvalue placement, the linear quadratic regulator, `sisotool`, `hinfsyn` and `rootlocus_pid_designer`, plus the linear quadratic estimator on the estimation side. State-space design fits comfortably on numpy and scipy, since placing poles and solving a Riccati equation is ordinary dense linear algebra.

Loop-shaping design is a different proposition. `sisotool` and `hinfsyn` sit on matrix packages the project does not vendor, and the README states plainly that some routines use slycot, a Python wrapper around FORTRAN routines, while many parts of the library will work without it. What that text does not do is name which specific routines disappear, so you cannot read the feature list and know what your install can do. The honest move is to call the routine and read the error.

The same gap applies on the nonlinear side. Optimization-based control, describing functions and differential flatness are listed, and the packaging metadata adds a `cvxopt>=1.2.0` optional dependency, yet nothing in the README maps a solver backend to a particular routine. Anyone budgeting a week for a describing function analysis should confirm the backend first.

Slycot version floor drifts away from the README's binary claim

Installing the optional layer is where friction concentrates. Conda is described as the easiest route, and both packages come from the conda-forge channel:

bash
conda install -c conda-forge control slycot

The README adds a specific warning about mixing conda-forge with the default conda channel, which can cause dependency problems, and advises pulling NumPy, SciPy and Matplotlib from conda-forge as well.

Pip is the other documented path, and its shape changes with the optional layer:

bash
pip install slycot   # optional; see below
pip install control

Installing slycot through pip needs a development environment with Python development files plus C and Fortran compilers, and the README says pip installation can be particularly complicated for Windows. Since Slycot 0.3.4, binaries exist for 64-bit Windows, OSX and Linux on conda-forge, so the conda route is the one with a documented prebuilt artefact.

Note the mismatch. The README justifies those prebuilt binaries by pointing at Slycot 0.3.4, while the optional dependency group asks for `slycot>=0.4.0`, and the pip line above installs slycot with no version constraint at all. Anyone pinning a reproducible environment has to close that gap themselves.

Pytest turns matrix subclass warnings into hard test errors

Testing is a plain pytest setup, and its configuration tells you more about the project than the feature list does. Both invocations are documented:

bash
pytest -v
pytest --pyargs control -v

From inside the source tree you run the first; against the installed package you run the second, and the second is the command that exercises what is actually on your path.

The configuration is strict in two ways. The addopts value sets `-ra --strict-markers`, so an unregistered marker is an error rather than a warning, and exactly four markers are registered:

yaml
markers = [
  "slycot: tests needing slycot",
  "noslycot: test needing slycot absent",
  "cvxopt: tests needing cvxopt",
  "pandas: tests needing pandas",
]

Tests are therefore partitioned by optional backend, with slycot and noslycot as deliberate opposites, so both a present and an absent slycot get exercised. Separately, the filterwarnings setting escalates any PendingDeprecationWarning mentioning a matrix subclass into an error, which means a dependency that starts warning about matrix behaviour breaks continuous integration as a failure rather than a warning.

The declared test extra is `pytest`, `pytest-timeout`, `ruff` and `numpydoc`. Numpydoc in that list means docstring format is part of what the suite checks, and the timeout plugin means a numerical routine that hangs fails as a timeout instead of stalling the machine.

One package directory, dynamic version, narrow lint coverage

Packaging shows a flat, low ceremony design. The distribution name is `control`, the build backend is `setuptools.build_meta`, and `setuptools-scm` supplies the version while writing it into a generated file:

yaml
packages = ["control"]
write_to = "control/_version.py"

Version is declared dynamic, so the number you import comes from the git checkout rather than a hardcoded field, and a source archive without git metadata needs a different route than `pip install .`.

That same block lists a single package, `control`. Nothing else is exported, which keeps imports short, makes a vendored copy easy to audit, and guarantees that the module you import is the module you read.

Lint coverage is narrower than the package it ships:

yaml
include = ['control/**.py', 'benchmarks/*.py', 'examples/*.py']

The configuration carries the comment TODO: expand to cover all code. Only the first pattern uses a double-star form while the other two use a single star, so the intent to widen checking past a narrow slice is explicit in the file rather than finished.

This project is BSD-3-Clause free software with no warranty, and the LICENSE file should accompany the code or any subset of it, or its text be copied into files. Its last push was on 2026-09-29, while the most recent tagged build, 0.10.2, dates from 2025-07-05, with 0.10.1 and 0.10.0 both landing in 2024.

Course notebooks run the library without a local install

The fastest way to judge fit is to read nothing and open something. The examples folder is reachable through the binder service, and the README hands you a hosted notebook pair for Google Colab:

bash
%pip install control
import control as ct

That alias is the convention the examples use, and matching it keeps shared code readable.

The example list is not filler. It holds numbered course sequences from `cds110-L1_servomech-python.ipynb` through `cds112-L4b_pvtol-lqr.ipynb`, walking through servo mechanisms, an inverted pendulum, LTI systems, state feedback, LQR tracking, estimation and trajectory generation on a kinematic car. Two filenames carry explicit limit names, `cds110-L8a_maglev-limits.ipynb` and `cds110-L8b_pvtol-complete-limits.ipynb`, which says something about how the maintainers treat the gap between a toy and a plant with a real envelope. A single port file, `bdalg-matlab.py`, carries MATLAB's block diagram algebra across.

Development follows the same plainness. Cloning the repository is the documented route to the latest source, and contributions travel back as a pull request with details in the Developer's Wiki. Research users have a citation to follow: an article on IEEE Explore from the 60th IEEE Conference on Decision and Control in 2021, pages 4875 to 4881, by Fuller, Greiner, Moore, Murray, van Paassen and Yorke.

Where scipy alone already covers the whole job

Consider the size of the job before installing anything. numpy, scipy and matplotlib are the only hard requirements, so for a script that evaluates one transfer function, plots it and checks one pole, writing directly against scipy is shorter than adding a framework. Reaching for `control` earns its keep when you want interconnection algebra, the `sisotool` dialog, root locus and margin computation, or estimator designs held in one object model. For a single plot it does not.

There are three concrete wrong-tool situations. Anything with a hard real-time loop has no path here, because the top-level entries cover documentation, examples, benchmarks and a conda recipe, with nothing resembling a runtime target or a code generator. Hardware in the loop work has that same gap. And with slycot missing, the loop-shaping routines you may have installed the library for are absent, on the one platform the README already calls complicated.

A benchmark harness is configured, since `asv.conf.json` sits beside `benchmarks/` at the top level. That tells you the maintainers track performance across revisions, but it publishes no figures in advance, so treat any performance expectation as unverified until you measure it against your own numpy, scipy and matplotlib versions.

Editorial conclusion

python-control suits control coursework, notebook work and offline plant analysis, where slycot installs easily and the flat `control` namespace keeps scripts short. It is a poor fit for embedded loops, hardware in the loop work or code generation, since `pyproject.toml` marks the package Development Status :: 4 - Beta and sets a Python floor of 3.10. Before adopting it, run `pytest --pyargs control -v` on your own machine, because the algorithm set you get depends on whether slycot is present on that machine.

Frequently asked questions

Does python-control work without slycot installed?

Many parts do, but the README states that some functionality is limited or absent when slycot is missing, and installation of slycot is recommended. The packaging metadata lists slycot>=0.4.0 as an optional dependency rather than a hard one.

What is the easiest way to install python-control?

Conda from the conda-forge channel with `conda install -c conda-forge control slycot`, which installs both the library and slycot into an existing environment. The README also warns that mixing conda-forge with the default conda channel can cause dependency problems.

Which Python versions and operating systems does python-control support?

The requirement is Python 3.10 or newer, with classifiers naming Python 3.10, 3.11 and 3.12. The classifiers also list Windows, POSIX, Unix and MacOS as supported operating systems.

How do I run the python-control test suite?

Run pytest -v inside the source directory, or pytest --pyargs control -v to test the installed package. Markers are strict, and the registered set is slycot, noslycot, cvxopt and pandas.

How is the python-control version number produced?

It is declared dynamic and written by setuptools-scm into control/_version.py from the git checkout, so a source copy without git metadata does not get a number the same way. The most recent tagged build listed is 0.10.2.

Official sources

  1. License: BSD-3-Clause
  2. Project website
  3. python-control/python-control on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/python-control-python-control.svg)](https://hysenlabs.com/projects/python-control-python-control)

Was this useful?