Control Systems Utility Library written in Typescript
This library consists in a number of utility functions for aiding in control systems analysis. The idea is to replicate and implement most of the basic functionalities present in Matlab's, Octave's and Python's similar packages, but with the practicality of having them in the web natively. This package uses it's own complex number implementation basic operations, but is, for the most part, compatible with complexjs and mathjs.
A demo of the library functionalities can be seen here
npm install control-systems-js
or
yarn add control-systems-js
The most basic structure for the library is the TransferFunction object. This interface contains a number of methods available for analysis of a given system. You can create a transfer function object with the transferFunction
method:
// Represents the following system:
// (s + 3)/(s^3 + 3s^2 + 5s + 1)
const tf = transferFunction({
numerator: [1, 3],
denominator: [1, 3, 5, 1],
});
The numerator and denominator properties represents the coefficients of the polynomial of a given function, ordered as the first index represents the coefficient of the highest order, similar to Matlab's implementation.
It's important to know that the library only accepts physically realizable control system, meaning that the denominator should have higher order than numerator
Also, it will throw an error if the passed system is not stable, as a couple of functions, especially the time-domain ones do not behave correctly if the function is unstable. Read more in the limitations section.
In the transfer function you have a number of methods:
We have two methods in the transfer function object, zero and pole which gives the values in which the system becomes zero and infinite respectively. The values of the poles and the zeros of a system determine whether the system is stable, and how well the system performs.
The zeros and poles are calculated when transferFunction is called and are available in the following methods:
tf.zero();
// output: [ { re: -3, im: 0 } ]
tf.pole();
/* output: [
{ re: -1.385458, im: -1.563885 },
{ re: -1.385458, im: 1.563885 },
{ re: -0.229083, im: -0 }
] */
We can also calculate the root locus of the SISO model sys, meaning, where the the closed-loop pole trajectories as a function of the feedback gain k (assuming negative feedback). This can be used to provide indirect information on the time and frequency responses.
const gains = [1, 10, 100];
const result = tf.rlocus(gains);
/* Output: {
gains: [1, 10, 100],
roots: [
[
{ x: -1, y: -1.73205 },
{ x: -0.34407, y: -3.64565 },
{ x: -0.0617972, y: -10.2294 }
],
[
{ x: -1, y: 1.73205 },
{ x: -0.34407, y: 3.64565 },
{ x: -0.0617972, y: 10.2294 }
]...
]
} */
In the data object, we have the location of each pole according to the passed gain.
It's also possible to calculate the time-domain response of a given transfer function with the methods step and impulse, where it calculates the values from a set of times.
const times = [0, 5, 10];
// Response from step input -> tf * 1/s
const stepResult = tf.step(times)
/* Output: [
{ x: 0, y: 5.0000346656356524e-17 },
{ x: 5, y: 1.982714789998949 },
{ x: 10, y: 2.6768427176048286 }
]
*/
const impulseResult = tf.impulse(times)
/* Output: [
{ x: 0, y: 1.0001579459073258e-8 },
{ x: 5, y: 0.23298395857008558 },
{ x: 10, y: 0.07467876616635728 }
]
*/
There are also frequency-domain analysis methods.
We can calculate both Bode Plot and Nyquist Plot points, for frequency analysis.
const frequencies = [0, 10, 100]
const bodeResult = tf.bode(frequencies)
/* Output: {
magnitude: [
{ x: 1, y: -3.0102999566398125 },
{ x: 10, y: -39.59041830161867 },
{ x: 100, y: -79.99565961654912 }
],
phase: [
{ x: 1, y: -98.130102354156 },
{ x: 10, y: -179.22853473338537 },
{ x: 100, y: -179.9991981797909 }
]
}
*/
const nyquistResult = tf.nyquist(frequencies)
/* Output: {
points: [
{ x: -0.1, y: -0.7 },
{ x: -0.010481892850193719, y: -0.00014114311811360206 },
{ x: -0.00010004998298870205, y: -1.4001397479468424e-9 }
],
correspondingPoints: [
{ x: -0.1, y: 0.7 },
{ x: -0.010481892850193719, y: 0.00014114311811360206 },
{ x: -0.00010004998298870205, y: 1.4001397479468424e-9 }
]
}
*/
There are some utilities functions, for returning the transfer function expression for other usages and also for converting it to a string
tf.getExpression()
/* Output: {
numerator: [ { re: 1, im: 0 }, { re: 3, im: 0 } ],
denominator: [
{ re: 1, im: 0 },
{ re: 3, im: 0 },
{ re: 5, im: 0 },
{ re: 1, im: 0 }
]
}
*/
tf.toString()
// Output: (s + 3) / (s^3 + 3s^2 + 5s + 1)
It's also possible to use all of the above functions in a standalone format, separately from the transferFunction object. These are the available functions
The library is in an early-stage, so it has a number of limitations of what it can do, especially comparing to more mature alternatives in other platforms. Here are a couple of them:
Generated using TypeDoc