Open-source project
JuliaControl/ControlSystems.jl avatar
JuliaControl/ControlSystems.jl

ControlSystems.jl is a MATLAB-shaped control toolbox written for Julia

A Control Systems Toolbox for Julia

585 stars93 forksJuliaNOASSERTION

At a glance

What is it?
ControlSystems.jl is a control systems design toolbox for Julia, and its most distinctive choice is that the whole thing is organised the way an engineer already thinks, with systems built in transfer function or state space form, analysed, synthesised and then plotted. The function list in the README reads like the chapter headings of a control textbook, and the example is a position controller for an electric motor with an inertial load.
Who is it for?
Adopt ControlSystems.jl if you do control work in Julia and want the same workflow you would have in MATLAB or Python, since the naming follows the textbooks rather than a new convention. Skip it if you need real-time code generation or hardware-in-the-loop, since this is a design and analysis toolbox rather than a deployment tool, and if you cannot use the Julia package ecosystem.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly Julia, according to GitHub's language statistics.

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

Editorial analysis

The command list is a textbook table of contents

The README lists the available commands by category, and the categories are the ones you would find in a control systems course.

Constructing systems is ss, tf, zpk and delay, so a linear system can be built in state space form, as a transfer function, as poles and zeros with gains, or with an explicit delay. Analysis covers poles, tzeros, norm, hinfnorm, linfnorm, ctrb, obsv, gangoffour, margin, markovparam, damp, dampreport, zpkdata, dcgain, covar, gram, sigma and sisomargin, which is the set of things you compute about a model to know whether it is well behaved.

Synthesis lists are, lyap, lqr, place, leadlink, laglink, leadlinkat, rstd, rstc, dab, balreal and baltrunc, so the Riccati equation solvers, the LQR design, pole placement, the lead and lag compensator designs and the model reduction routines.

Then there is a PID design group with pid, stabregionPID, loopshapingPI, pidplots and placePI, which is a group most engineering toolboxes leave out and which reflects that PID is still what most industrial controllers run.

Time and frequency response covers step, impulse, lsim, freqresp, evalfr, bode and nyquist. Plotting has the matching plot functions for each of those plus marginplot, gangoffourplot, pzmap, nicholsplot, rlocus and leadlinkcurve. And a final group of minreal, sminreal and c2d for model reduction and conversion.

The structure is the point. The names are the ones in the textbooks, so a control engineer arriving from MATLAB or from a Python toolbox does not have to learn a new vocabulary, and a search for how to do something finds the function under the name the literature uses.

The project is Julia, with the last push on 2026-10-13 and releases v1.16.0 from 2026-10-01, v1.15.5 from 2026-05-18 and v1.15.4 from 2026-04-27. The documentation is published with a stable and a dev version, and there is an introductory video series.

The motor example shows the whole workflow in twenty lines

The example in the README is a position controller for an electric motor with an inertial load, and it is short enough to read in one go.

julia
using ControlSystems

# Motor parameters
J = 2.0
b = 0.04
K = 1.0
R = 0.08
L = 1e-4

# Create the model transfer function
s = tf("s")
P = K/(s*((J*s + b)*(L*s + R) + K^2))
# This generates the system
# TransferFunction:
#                1.0
# ---------------------------------
# 0.0002s^3 + 0.160004s^2 + 1.0032s
#
#Continuous-time transfer function model

# Create an array of closed loop systems for different values of Kp
CLs = [kp*P/(1 + kp*P) for kp ∈ [1, 5, 15]];

# Plot the step response of the controllers
# Any keyword arguments supported in Plots.jl can be supplied
using Plots
plot(step.(CLs, 5), label=["Kp = 1" "Kp = 5" "Kp = 15"])

The first block is just the physical parameters of the motor. J is the inertia, b is the friction, K is the motor constant, R is the resistance and L is the inductance, all given as numbers.

Then the model. `s = tf("s")` creates the Laplace variable as a transfer function, and the plant is written as a rational function in it, which is the classical way of writing a motor model. The comment shows what the toolbox produced: a continuous-time transfer function with a cubic denominator, printed in the form an engineer would check by hand.

The next part is the interesting bit for anyone learning the language. The closed-loop systems for three different values of Kp are built with a comprehension, `CLs = [kp*P/(1 + kp*P) for kp ∈ [1, 5, 15]]`, and then the step responses are computed with a broadcast, `step.(CLs, 5)`, where the 5 is the simulation time. The plotting call then labels the three curves.

Two things about that are worth pulling out. The language handles the array of models and the array of responses as ordinary values, so the code reads like the mathematics rather than like a loop over a list of controller gains. And the comment notes that any keyword arguments supported in Plots.jl can be supplied, which is how the toolbox stays out of the plotting business: it produces the results and hands them to the Julia plotting ecosystem.

That separation is a recurring theme. The toolbox computes, Plots.jl draws.

Two representations, and the toolbox converts between them

The usage section says the toolbox works similarly to other major computer-aided control systems design toolboxes, and then makes the one design decision explicit: systems can be created in either a transfer function or a state space representation.

That matters more than it sounds. A transfer function is what you get from system identification on measured data and what you use for frequency domain analysis, since the frequency response is a property of the transfer function directly. A state space representation is what you need for pole placement, for observers, for Kalman filtering and for anything with multiple inputs and outputs, because the transfer function assumes a single input and a single output.

So the toolbox has to live in both, and the conversion is not trivial. A transfer function is a rational function and a state space realisation is a matrix pair, and the mapping between them involves a matrix inversion that fails for a system with an unobservable or uncontrollable part. The command list includes minreal and sminreal for reducing a realisation to its minimal form, which is the operation that removes the parts of a state space model that do not affect the input and output behaviour.

The delay command is the other representation-related piece. Discrete and continuous time systems are different objects, and c2d is the conversion between them. A delay in a loop is a modelling headache rather than an algebra one, because a pure delay has an infinite transfer function, which is why the delay function exists as a first-class constructor rather than something you approximate.

The command list also includes the model reduction routines balreal and baltrunc, which is what you use when a state space model has states you do not need.

The examples folder makes the same point from the other direction. There is a delayed linear time invariant system example and a delayed time response example, a DC motor LQG design example, an automatic differentiation example, a symbolic computations example and a describing function example for nonlinear analysis.

The package fits a wider JuliaControl ecosystem

The repository layout shows a package with the usual parts plus a couple that hint at a broader organisation.

There is a src/ directory for the implementation, a lib/ directory, a test/ directory, a docs/ directory, an example/ directory and a Project.toml. The CITATION.bib file at the root is the detail worth noting, since it means the package is set up to be cited properly in a paper, which for a research-adjacent tool is a reasonable thing to have.

The name of the repository is under the JuliaControl organisation, which suggests this is one package in a family. The homepages of the documentation point at juliacontrol.github.io, and the ControlExamples.jl repository is linked from the additional examples section alongside the examples folder in this repository.

So the pattern is that the toolbox itself is here, the worked examples live in a separate repository, and the documentation site is generated separately for stable and dev versions.

The other repository detail is the examples folder, and the file names are a good index of what the toolbox can do. There is dc_motor_lqg_design.jl, which is a full design problem combining a plant model with an optimal controller and a state estimator. There is autodiff.jl, which is about using automatic differentiation in a control context. There is symbolic_computations.jl, which is about the symbolic side of the algebra. There is describing_function.jl, which is a nonlinear analysis technique. And there is example_pid_design.md, which is a written walkthrough of the PID design workflow rather than a script.

The automatic differentiation example is the one that makes me think this package is more interesting than a port. A control toolbox in a language with automatic differentiation can differentiate through a simulation, which is useful for tuning and for optimisation-based design, and that is not something the older toolboxes could easily offer.

Installation is a single Pkg call in the REPL, and every function has a docstring that can be viewed from the REPL with the question mark prefix, so the documentation is available without leaving the environment.

Where this sits against MATLAB and against Python

The comparison is with the MATLAB Control System Toolbox and with the Python equivalents, and the honest answer depends on why you are in Julia at all.

Against MATLAB, ControlSystems.jl is the same mathematics with a different syntax and a different ecosystem. The functions are named the same way, the objects are the same kind of object, and the difference is that the results are ordinary Julia values that compose with everything else in the language rather than being a separate world. If your reason for using Julia is that you need the simulation results inside a larger optimisation or machine learning pipeline, that difference is the whole argument.

Against Python, the comparison is with the combination of python-control and the scientific stack around it. The Python route is more commonly used and has more third-party examples, but the Julia version has the advantage of being a single coherent package rather than an assembly, and Julia's multiple dispatch makes some of the awkward cases in control, where the same function name behaves differently for continuous and discrete or for transfer function and state space, less awkward to write.

What you give up in either case is the ecosystem around the toolbox rather than the toolbox itself. MATLAB has Simulink, which is a modelling and simulation environment rather than a library, and no Julia package is going to replace that. Control theory practitioners with hardware in the loop usually end up with a generated real-time implementation and a MATLAB or Simulink model side by side, and that arrangement is not unusual.

The documentation is well organised for a Julia package, with the stable and dev sites, docstrings for every function, a video series for people starting from scratch, and separate example repositories. For an academic or industrial controls course, the function catalogue in the README is itself a useful syllabus map.

The practical caveat is the same as for any scientific package: check the version compatibility of the wider ecosystem before you build on it, because these packages are versioned independently of each other and Julia's package manager is strict about that in a way pip often is not.

Editorial conclusion

Adopt ControlSystems.jl if you do control work in Julia and want the same workflow you would have in MATLAB or Python, since the naming follows the textbooks rather than a new convention. Skip it if you need real-time code generation or hardware-in-the-loop, since this is a design and analysis toolbox rather than a deployment tool, and if you cannot use the Julia package ecosystem. Verify first which Julia version your environment has, that the packages you need are in the right compatibility range, because the package is versioned separately from the wider JuliaControl ecosystem, and how you want plotting to work, since the toolbox integrates with Plots.jl and you can supply any keyword arguments it supports.

Frequently asked questions

How do I install ControlSystems.jl and read its documentation?

Install it in the Julia REPL with `using Pkg; Pkg.add("ControlSystems")`. All functions have docstrings that can be viewed from the REPL, for example with `?tf`. A documentation website is published at juliacontrol.github.io with stable and dev versions, and there is an introductory video series.

What kinds of control problems can ControlSystems.jl handle?

Systems can be built in transfer function or state space form with ss, tf, zpk and delay, then analysed with functions including poles, norm, ctrb, obsv, margin, gram and sigma, synthesised with are, lyap, lqr, place and the lead and lag designs, designed as PID with pid, stabregionPID and loopshapingPI, and simulated in both time and frequency domain with step, impulse, lsim, freqresp, bode and nyquist.

Does ControlSystems.jl produce plots itself?

The toolbox provides the plotting functions such as bodeplot, nyquistplot, sigmaplot, marginplot, gangofourplot, pzmap, nicholsplot, pidplots and rlocus, and the example notes that any keyword arguments supported in Plots.jl can be supplied. Plotting is done through the Julia plotting ecosystem, and the toolbox computes the results.

Official sources

  1. Issues
  2. JuliaControl/ControlSystems.jl on GitHub
  3. Project website
  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/juliacontrol-controlsystems-jl.svg)](https://hysenlabs.com/projects/juliacontrol-controlsystems-jl)

Was this useful?