engineeringkit.control_systems#
Classical stability, PID responses, state feedback, LQR and estimation.
engineeringkit.control_systems: feedback, stability, frequency response, state space and optimal control.
Black’s feedback amplifier and Minorsky’s PID controller with Ziegler-Nichols tuning; Maxwell’s characteristic roots, the Routh and Hurwitz tests and Lyapunov’s direct method; Nyquist’s encirclement criterion, Bode plots and stability margins; Evans’s root locus; Hazen’s second-order servo specifications; zero-order-hold discretisation; Kalman’s controllability and observability, pole placement and Luenberger observers; and the LQR, discrete LQR and Kalman-Bucy Riccati designs. Models are continuous-time and SISO unless stated; SciPy supplies the LTI objects, Riccati/Lyapunov solvers, pole placement and step responses.
- class engineeringkit.control_systems.PID(kp, ki=0.0, kd=0.0)[source]#
Bases:
objectIdeal parallel PID controller \(C(s) = K_p + K_i/s + K_d s\).
Derivative action is ideal (no roll-off filter), and there is no actuator saturation or anti-windup. With
ki == 0no integrator is included.Examples
>>> from engineeringkit.control_systems import transfer_function >>> r = PID(1, 1).simulate(transfer_function([1], [1, 1]), np.linspace(0, 5, 51)) >>> round(r.summary()["final"], 4) 0.9933
- Parameters:
kp (float)
ki (float)
kd (float)
- kd: float = 0.0#
Derivative gain [input s/error].
- Type:
float
- ki: float = 0.0#
Integral gain [input/(error s)].
- Type:
float
- kp: float#
Proportional gain [input/error].
- Type:
float
- simulate(plant, T=None)[source]#
Unit-step reference response of the closed loop, as a
ResponseResult.
- class engineeringkit.control_systems.ResponseResult(t, y)[source]#
Bases:
objectTime response of a simulated control loop.
- Parameters:
t (ndarray)
y (ndarray)
- t: ndarray#
Time samples [s].
- Type:
ndarray
- y: ndarray#
Output at each sample, in output units per unit reference.
- Type:
ndarray
- engineeringkit.control_systems.as_polynomial(coeffs, name='coeffs', min_degree=0)[source]#
Validate a descending-power polynomial and strip leading zeros.
- Parameters:
coeffs (array_like) – Real coefficients, highest power first.
name (str, optional) – Argument name used in error messages.
min_degree (int, optional) – Smallest admissible degree after stripping leading zeros.
- Returns:
Coefficients with a nonzero leading term.
- Return type:
ndarray
- Raises:
ValueError – If the coefficients are not a finite real vector, are all zero, or the degree is below
min_degree.
Examples
>>> as_polynomial([0, 1, 3, 2]) array([1., 3., 2.])
- engineeringkit.control_systems.bode(sys, w=None)[source]#
Bode magnitude and phase of a continuous-time model.
- Parameters:
sys (scipy.signal.lti) – Continuous-time model.
w (array_like, optional) – Angular frequencies [rad/s]; chosen automatically if omitted.
- Returns:
(w, magnitude, phase): angular frequency [rad/s], gain [dB] and phase [deg].- Return type:
tuple of ndarray
Notes
Wraps
scipy.signal.bode(). See Control systems: matrix contracts and numerical stability.References
H. W. Bode, Network Analysis and Feedback Amplifier Design (Van Nostrand, 1945).
Examples
>>> from engineeringkit.control_systems import transfer_function >>> w, mag, phase = bode(transfer_function([1], [1, 1]), [1.0]) >>> round(float(mag[0]), 3), round(float(phase[0]), 1) (-3.01, -45.0)
- engineeringkit.control_systems.controllability_matrix(A, B)[source]#
Kalman controllability matrix \([B,\ AB,\ \dots,\ A^{n-1}B]\).
- Parameters:
A (array_like) – State matrix, shape
(n, n).B (array_like) – Input matrix, shape
(n, m).
- Returns:
Shape
(n, n m).- Return type:
ndarray
Notes
See Control systems: matrix contracts and numerical stability.
References
R. E. Kalman, “On the general theory of control systems,” Proc. 1st IFAC Congress (Moscow, 1960) 481-492.
Examples
>>> controllability_matrix([[0, 1], [0, 0]], [[0], [1]]).tolist() [[0.0, 1.0], [1.0, 0.0]]
- engineeringkit.control_systems.dc_gain(sys)[source]#
Steady-state gain \(G(0)\) for a constant input.
- Parameters:
sys (scipy.signal.lti) – SISO continuous-time model.
- Returns:
Output per unit input at zero frequency.
- Return type:
float
- Raises:
ValueError – If the model has a pole at \(s = 0\) (an integrator), so the gain is unbounded.
Notes
The steady state is reached only if the system is stable. See Control systems: matrix contracts and numerical stability.
Examples
>>> dc_gain(transfer_function([2, 2], [1, 3, 2])) 1.0
- engineeringkit.control_systems.discretize_zoh(A, B, dt)[source]#
Exact zero-order-hold discretisation \((A_d, B_d)\) of \(\dot x = Ax + Bu\).
- Parameters:
A (array_like) – Continuous state matrix, shape
(n, n)[1/s].B (array_like) – Continuous input matrix, shape
(n, m).dt (float) – Sampling period [s], positive.
- Returns:
(Ad, Bd)for \(x_{k+1} = A_d x_k + B_d u_k\).- Return type:
tuple of ndarray
Notes
Both matrices come from one matrix exponential of the augmented matrix \(\begin{bmatrix}A & B\\ 0 & 0\end{bmatrix} T\), which is valid even when \(A\) is singular. See Control systems: matrix contracts and numerical stability.
References
J. R. Ragazzini and L. A. Zadeh, “The analysis of sampled-data systems,” Trans. AIEE 71 (1952) 225-234.
Examples
>>> Ad, Bd = discretize_zoh([[0.0]], [[1.0]], 0.5) >>> Ad.tolist(), Bd.tolist() ([[1.0]], [[0.5]])
- engineeringkit.control_systems.dlqr(A, B, Q, R)[source]#
Discrete-time infinite-horizon linear-quadratic regulator.
Minimises \(\sum_{k \ge 0} (x_k^\mathsf{T} Q x_k + u_k^\mathsf{T} R u_k)\) over \(u_k = -K x_k\) for \(x_{k+1} = A x_k + B u_k\); the steady state of Bellman’s backward dynamic-programming recursion.
- Parameters:
A (array_like) – Discrete state matrix, shape
(n, n).B (array_like) – Discrete input matrix, shape
(n, m).Q (array_like) – Symmetric positive semidefinite state weight, shape
(n, n).R (array_like) – Symmetric positive definite input weight, shape
(m, m).
- Returns:
(K, P, poles): gain \(K = (R + B^\mathsf{T}PB)^{-1}B^\mathsf{T}PA\), the discrete Riccati solution \(P\) and the closed-loop eigenvalues (inside the unit circle when stabilising).- Return type:
tuple
Notes
See Control systems: matrix contracts and numerical stability.
References
Bellman, Dynamic Programming (Princeton University Press, 1957).
Examples
>>> K, P, poles = dlqr([[1.0]], [[1.0]], [[1.0]], [[1.0]]) >>> round(float(P[0, 0]), 6), round(float(K[0, 0]), 6) (1.618034, 0.618034)
- engineeringkit.control_systems.feedback(G, H=1.0, sign=-1)[source]#
Closed-loop transfer function of a feedback loop, \(G/(1 - \sigma GH)\).
- Parameters:
G (scipy.signal.lti) – Forward-path model.
H (scipy.signal.lti or float, optional) – Feedback-path model or constant gain (unity feedback by default).
sign ({-1, 1}, optional) – Feedback sign \(\sigma\);
-1is negative feedback.
- Returns:
Closed-loop model from reference to output.
- Return type:
scipy.signal.TransferFunction
Notes
Pole-zero cancellations are not removed. See Control systems: matrix contracts and numerical stability.
References
H. S. Black, “Stabilized feedback amplifiers,” Bell Syst. Tech. J. 13 (1934) 1-18.
Examples
>>> T = feedback(transfer_function([1000], [1]), 0.01) >>> round(dc_gain(T), 3) 90.909
- engineeringkit.control_systems.feedforward_gain(A, B, C, K)[source]#
Reference precompensator \(N\) giving unit DC gain under SISO state feedback.
With \(u = -Kx + Nr\), the closed loop \(\dot x = (A - BK)x + BNr\) tracks a constant reference when \(N = -1/\left(C (A - BK)^{-1} B\right)\).
- Parameters:
A (array_like) – State matrix, shape
(n, n).B (array_like) – Input matrix, shape
(n, 1).C (array_like) – Output matrix, shape
(1, n).K (array_like) – State-feedback gain, shape
(1, n).
- Returns:
Precompensator gain [input/reference units].
- Return type:
float
- Raises:
ValueError – If the system is not SISO, or the closed loop has a zero at the origin so no finite \(N\) exists.
Notes
Exact only for a perfect model; integral action is needed for robust tracking. See Control systems: matrix contracts and numerical stability.
Examples
>>> feedforward_gain([[-1]], [[1]], [[1]], [[2]]) 3.0
- engineeringkit.control_systems.hurwitz_determinants(coeffs)[source]#
Leading principal minors \(\Delta_1, \dots, \Delta_n\) of the Hurwitz matrix.
For \(a_0 s^n + a_1 s^{n-1} + \dots + a_n\) with \(a_0 > 0\), all roots lie in the open left half-plane if and only if every \(\Delta_k > 0\). The Hurwitz matrix has entries \(H_{ij} = a_{2j-i}\) (1-based, \(a_k = 0\) outside \(0 \le k \le n\)).
- Parameters:
coeffs (array_like) – Real coefficients in descending powers of \(s\), degree at least one. They are normalised by the leading coefficient.
- Returns:
The \(n\) determinants.
- Return type:
ndarray
Notes
On the stability boundary \(\Delta_{n-1} = 0\) (a pair of imaginary roots) or \(\Delta_n = 0\) (a root at the origin). See Control systems: matrix contracts and numerical stability.
References
A. Hurwitz, “Ueber die Bedingungen, unter welchen eine Gleichung nur Wurzeln mit negativen reellen Theilen besitzt,” Math. Ann. 46 (1895) 273-284.
Examples
>>> hurwitz_determinants([1, 3, 3, 1]).round(6).tolist() [3.0, 8.0, 8.0]
- engineeringkit.control_systems.impulse_response(sys, T=None)[source]#
Response to a unit impulse input from rest.
- Parameters:
sys (scipy.signal.lti) – Continuous-time model.
T (array_like, optional) – Time samples [s]; chosen automatically if omitted.
- Returns:
(t, h): time [s] and impulse response [output/(input s)].- Return type:
tuple of ndarray
Notes
Wraps
scipy.signal.impulse(). See Control systems: matrix contracts and numerical stability.Examples
>>> t, h = impulse_response(transfer_function([1], [1, 1]), [0.0, 1.0]) >>> round(float(h[-1]), 4) 0.3679
- engineeringkit.control_systems.is_controllable(A, B)[source]#
Whether the controllability matrix has full state rank.
- Parameters:
A (array_like) – State matrix, shape
(n, n).B (array_like) – Input matrix, shape
(n, m).
- Returns:
Trueif every state can be steered by the inputs.- Return type:
bool
Notes
Rank is computed numerically with SVD tolerances. See Control systems: matrix contracts and numerical stability.
Examples
>>> is_controllable([[-1, 0], [0, -1]], [[1], [1]]) False
- engineeringkit.control_systems.is_observable(A, C)[source]#
Whether the observability matrix has full state rank.
- Parameters:
A (array_like) – State matrix, shape
(n, n).C (array_like) – Output matrix, shape
(p, n).
- Returns:
Trueif every state can be reconstructed from the outputs.- Return type:
bool
Notes
See Control systems: matrix contracts and numerical stability.
Examples
>>> is_observable([[0, 1], [0, 0]], [[1, 0]]) True
- engineeringkit.control_systems.kalman_gain(A, C, Q, R)[source]#
Steady-state Kalman-Bucy estimator gain for continuous-time measurements.
For \(\dot x = Ax + w\), \(y = Cx + v\) with white process and measurement noise of spectral densities \(Q\) and \(R\), the estimator \(\dot{\hat x} = A\hat x + L(y - C\hat x)\) with \(L = PC^\mathsf{T}R^{-1}\) minimises the steady error covariance \(P\).
- Parameters:
A (array_like) – State matrix, shape
(n, n)[1/s].C (array_like) – Output matrix, shape
(p, n).Q (array_like) – Process-noise spectral density, symmetric positive semidefinite, shape
(n, n).R (array_like) – Measurement-noise spectral density, symmetric positive definite, shape
(p, p).
- Returns:
(L, P, poles): observer gain, error covariance and estimator poles [1/s].- Return type:
tuple
Notes
\(P\) solves \(AP + PA^\mathsf{T} - PC^\mathsf{T}R^{-1}CP + Q = 0\). \(Q\) and \(R\) are spectral densities, not sampled covariances. See Control systems: matrix contracts and numerical stability.
References
R. E. Kalman and R. S. Bucy, “New results in linear filtering and prediction theory,” J. Basic Eng. 83 (1961) 95-108.
Examples
>>> L, P, poles = kalman_gain([[0]], [[1]], [[4]], [[1]]) >>> L.round(6).tolist(), P.round(6).tolist() ([[2.0]], [[2.0]])
- engineeringkit.control_systems.lqr(A, B, Q, R)[source]#
Continuous-time linear-quadratic regulator.
Minimises \(J = \int_0^\infty (x^\mathsf{T} Q x + u^\mathsf{T} R u)\,dt\) over \(u = -Kx\) for \(\dot x = Ax + Bu\).
- Parameters:
A (array_like) – State matrix, shape
(n, n)[1/s].B (array_like) – Input matrix, shape
(n, m).Q (array_like) – Symmetric positive semidefinite state weight, shape
(n, n).R (array_like) – Symmetric positive definite input weight, shape
(m, m).
- Returns:
(K, P, poles): gain \(K = R^{-1} B^\mathsf{T} P\), the Riccati solution \(P\) (so the optimal cost from \(x_0\) is \(x_0^\mathsf{T} P x_0\)) and the closed-loop poles [1/s].- Return type:
tuple
Notes
\(P\) solves \(A^\mathsf{T}P + PA - PBR^{-1}B^\mathsf{T}P + Q = 0\). A stabilising solution needs \((A, B)\) stabilisable and \((Q^{1/2}, A)\) detectable; SciPy errors are not masked. See Control systems: matrix contracts and numerical stability.
References
R. E. Kalman, “Contributions to the theory of optimal control,” Bol. Soc. Mat. Mexicana 5 (1960) 102-119.
Examples
>>> K, P, poles = lqr([[0]], [[1]], [[4]], [[1]]) >>> K.round(6).tolist(), poles.real.round(6).tolist() ([[2.0]], [-2.0])
- engineeringkit.control_systems.lyapunov_equation(A, Q=None)[source]#
Solve the continuous Lyapunov equation \(A^\mathsf{T} P + P A = -Q\).
If \(A\) is stable, \(V(x) = x^\mathsf{T} P x\) is a Lyapunov function: it is positive and decreases at the rate \(\dot V = -x^\mathsf{T} Q x\) along every trajectory of \(\dot x = Ax\).
- Parameters:
A (array_like) – State matrix, shape
(n, n)[1/s].Q (array_like, optional) – Symmetric positive semidefinite decay-rate matrix, shape
(n, n); the identity by default.
- Returns:
Symmetric solution \(P\).
- Return type:
ndarray
- Raises:
ValueError – If two eigenvalues of \(A\) sum to zero, so the equation has no unique solution.
Notes
See Control systems: matrix contracts and numerical stability.
References
A. M. Lyapunov, The General Problem of the Stability of Motion (Kharkov, 1892; English translation, Taylor & Francis, 1992).
Examples
>>> lyapunov_equation([[-1.0]]).tolist() [[0.5]]
- engineeringkit.control_systems.lyapunov_stable(A)[source]#
Asymptotic stability of \(\dot x = Ax\) by Lyapunov’s direct method.
- Parameters:
A (array_like) – State matrix, shape
(n, n)[1/s].- Returns:
Trueif \(A^\mathsf{T} P + PA = -I\) has a positive definite solution \(P\).- Return type:
bool
Notes
A singular equation (eigenvalues summing to zero, e.g. an undamped oscillator) means the origin is not asymptotically stable. See Control systems: matrix contracts and numerical stability.
Examples
>>> lyapunov_stable([[0, 1], [-2, -3]]), lyapunov_stable([[0, 1], [-1, 0]]) (True, False)
- engineeringkit.control_systems.num_den(sys)[source]#
Numerator and denominator polynomials of a SISO continuous-time model.
- Parameters:
sys (scipy.signal.lti) – A
TransferFunction, or any SciPy LTI model that converts to one (state space, zeros-poles-gain).- Returns:
(num, den)in descending powers of \(s\).- Return type:
tuple of ndarray
- Raises:
ValueError – If the model is not single-input single-output.
Examples
>>> num_den(signal.TransferFunction([2, 2], [1, 3, 2])) (array([2., 2.]), array([1., 3., 2.]))
- engineeringkit.control_systems.nyquist(sys, w)[source]#
Complex frequency response \(G(j\omega)\).
- Parameters:
sys (scipy.signal.lti) – SISO continuous-time model.
w (float or array_like) – Angular frequencies [rad/s]; negative values give the mirror image.
- Returns:
Output/input gain at each frequency.
- Return type:
complex or ndarray of complex
Notes
See Control systems: matrix contracts and numerical stability.
Examples
>>> from engineeringkit.control_systems import transfer_function >>> complex(np.round(nyquist(transfer_function([2, 2], [1, 3, 2]), 2.0), 12)) (0.5-0.5j)
- engineeringkit.control_systems.nyquist_encirclements(sys, w=None)[source]#
Net clockwise encirclements of \(-1\) by the Nyquist plot of a loop.
Nyquist’s criterion: the closed loop \(L/(1+L)\) has \(Z = N + P\) right-half-plane poles, where \(P\) is the number of open-loop right-half-plane poles and \(N\) the value returned here.
- Parameters:
sys (scipy.signal.lti) – Proper open-loop model \(L(s)\) with no poles on the imaginary axis.
w (array_like, optional) – Positive angular frequencies [rad/s] spanning all features of \(L\); defaults to 40001 points from \(10^{-4}\) to \(10^4\).
- Returns:
Clockwise encirclements of \(-1\) (negative if counter-clockwise).
- Return type:
int
- Raises:
ValueError – If \(L\) is improper, has imaginary-axis poles, or passes through \(-1\) on the grid.
Notes
The plot is traced for \(\omega\) from \(-\infty\) to \(\infty\); for a proper loop the infinite semicircle maps to a single point. A grid that misses a fast phase swing can miscount. See Control systems: matrix contracts and numerical stability.
References
H. Nyquist, “Regeneration theory,” Bell Syst. Tech. J. 11 (1932) 126-147.
Examples
>>> from engineeringkit.control_systems import transfer_function >>> nyquist_encirclements(transfer_function([12], [1, 3, 3, 1])) 2
- engineeringkit.control_systems.observability_matrix(A, C)[source]#
Kalman observability matrix \([C;\ CA;\ \dots;\ CA^{n-1}]\).
- Parameters:
A (array_like) – State matrix, shape
(n, n).C (array_like) – Output matrix, shape
(p, n).
- Returns:
Shape
(n p, n).- Return type:
ndarray
Notes
See Control systems: matrix contracts and numerical stability.
Examples
>>> observability_matrix([[0, 1], [0, 0]], [[1, 0]]).tolist() [[1.0, 0.0], [0.0, 1.0]]
- engineeringkit.control_systems.observer_gain(A, C, desired_poles)[source]#
Luenberger observer gain \(L\) placing the eigenvalues of \(A - LC\).
The observer \(\dot{\hat x} = A\hat x + Bu + L(y - C\hat x)\) makes the estimation error obey \(\dot e = (A - LC)e\).
- Parameters:
A (array_like) – State matrix, shape
(n, n).C (array_like) – Output matrix, shape
(p, n);(A, C)must be observable.desired_poles (array_like) –
nerror-dynamics poles [1/s], conjugate pairs for complex values.
- Returns:
Gain \(L\), shape
(n, p).- Return type:
ndarray
Notes
Computed by duality, placing the poles of \(A^\mathsf{T} - C^\mathsf{T} L^\mathsf{T}\). See Control systems: matrix contracts and numerical stability.
References
D. G. Luenberger, “Observing the state of a linear system,” IEEE Trans. Mil. Electron. 8 (1964) 74-80.
Examples
>>> observer_gain([[0, 1], [0, 0]], [[1, 0]], [-1, -2]).round(6).tolist() [[3.0], [2.0]]
- engineeringkit.control_systems.poles(sys)[source]#
Poles of a SISO model, the roots of its denominator.
- Parameters:
sys (scipy.signal.lti) – SISO continuous-time model.
- Returns:
Poles [1/s].
- Return type:
ndarray of complex
Notes
The system is asymptotically stable when every pole has a negative real part. See Control systems: matrix contracts and numerical stability.
Examples
>>> np.sort(poles(transfer_function([1], [1, 3, 2]))).real.tolist() [-2.0, -1.0]
- engineeringkit.control_systems.root_locus(num, den, gains)[source]#
Closed-loop characteristic roots of \(D(s) + K N(s)\) for each gain.
- Parameters:
num (array_like) – Open-loop numerator and denominator, descending powers of \(s\). The open loop must be strictly proper so the number of roots is constant.
den (array_like) – Open-loop numerator and denominator, descending powers of \(s\). The open loop must be strictly proper so the number of roots is constant.
gains (array_like) – Nonempty vector of loop gains \(K\) [-].
- Returns:
Shape
(len(gains), len(den) - 1); rowiholds the roots forgains[i]. Roots within a row are not tracked along branches.- Return type:
ndarray of complex
Notes
See Control systems: matrix contracts and numerical stability.
References
W. R. Evans, “Graphical analysis of control systems,” Trans. AIEE 67 (1948) 547-551.
Examples
>>> np.sort(root_locus([1], [1, 3, 2], [0.25])[0].real).tolist() [-1.5, -1.5]
- engineeringkit.control_systems.routh_first_column(coeffs)[source]#
First column of the Routh array of a real polynomial.
The number of sign changes down the column equals the number of roots in the open right half-plane.
- Parameters:
coeffs (array_like) – Real coefficients in descending powers of \(s\), with a nonzero leading coefficient and degree at least one.
- Returns:
First-column entries for rows \(s^n\) down to \(s^0\), normalised so the leading coefficient is one.
- Return type:
ndarray
Notes
A row of zeros is replaced by the derivative of the auxiliary polynomial formed from the row above. A zero pivot is replaced by a small positive \(\varepsilon\), so this is a numerical teaching method rather than a symbolic proof for roots on or near the imaginary axis. See Control systems: matrix contracts and numerical stability.
References
E. J. Routh, A Treatise on the Stability of a Given State of Motion (Macmillan, 1877).
Examples
>>> routh_first_column([1, 3, 3, 1]).round(4).tolist() [1.0, 3.0, 2.6667, 1.0]
- engineeringkit.control_systems.routh_stable(coeffs)[source]#
Whether every root of a polynomial lies strictly in the left half-plane.
- Parameters:
coeffs (array_like) – Real coefficients in descending powers of \(s\).
- Returns:
Trueif the Routh column has no sign change and no root lies on the imaginary axis; marginal stability counts as unstable.- Return type:
bool
Notes
Routh signs alone cannot distinguish the auxiliary-row marginal case, so root locations confirm it with a relative tolerance. See Control systems: matrix contracts and numerical stability.
Examples
>>> routh_stable([1, 3, 3, 1]), routh_stable([1, 0, 1]) (True, False)
- engineeringkit.control_systems.second_order_metrics(zeta, omega_n)[source]#
Peak time, percent overshoot and 2 % settling time of an underdamped second-order step response.
- Parameters:
zeta (float or array_like) – Damping ratio [-], strictly between 0 and 1.
omega_n (float or array_like) – Undamped natural frequency [rad/s], positive.
- Returns:
peak_time\(\pi/\omega_d\) [s],percent_overshoot\(100\,e^{-\pi\zeta/\sqrt{1-\zeta^2}}\) [%] andsettling_time_2pct\(4/(\zeta\omega_n)\) [s], with \(\omega_d = \omega_n\sqrt{1-\zeta^2}\).- Return type:
dict
Notes
Peak time and overshoot are exact; the settling time is the usual envelope estimate, not the exact 2 % crossing. See Control systems: matrix contracts and numerical stability.
References
H. L. Hazen, “Theory of servo-mechanisms,” J. Franklin Inst. 218 (1934) 279-331.
Examples
>>> m = second_order_metrics(0.5, 2.0) >>> round(float(m["percent_overshoot"]), 2), float(m["settling_time_2pct"]) (16.3, 4.0)
- engineeringkit.control_systems.stability_margins(sys, w=None)[source]#
Gain and phase margins from the first crossovers of a loop’s frequency response.
- Parameters:
sys (scipy.signal.lti) – SISO open-loop model \(L(s)\).
w (array_like, optional) – Positive angular frequencies [rad/s]; defaults to 20000 points from \(10^{-4}\) to \(10^4\).
- Returns:
gain_margin[-] (infwithout a phase crossover),phase_margin_deg[deg] (nanwithout a gain crossover),gain_cross_frequencyandphase_cross_frequency[rad/s].- Return type:
dict
Notes
Crossings are interpolated linearly in log-frequency between grid points. Only the first crossover of each kind is reported, so loops with multiple crossovers, time delays or negative gain need separate analysis. See Control systems: matrix contracts and numerical stability.
References
H. W. Bode, “Relations between attenuation and phase in feedback amplifier design,” Bell Syst. Tech. J. 19 (1940) 421-454.
Examples
>>> from engineeringkit.control_systems import transfer_function >>> m = stability_margins(transfer_function([2], [1, 3, 2, 0])) >>> round(m["gain_margin"], 2), round(m["phase_cross_frequency"], 3) (3.0, 1.414)
- engineeringkit.control_systems.state_feedback_poles(A, B, desired_poles)[source]#
State-feedback gain \(K\) placing the eigenvalues of \(A - BK\).
- Parameters:
A (array_like) – State matrix, shape
(n, n).B (array_like) – Input matrix, shape
(n, m);(A, B)must be controllable.desired_poles (array_like) –
nclosed-loop poles [1/s]; complex poles must come in conjugate pairs.
- Returns:
Gain \(K\), shape
(m, n), for the control law \(u = -Kx\).- Return type:
ndarray
Notes
Wraps
scipy.signal.place_poles(). See Control systems: matrix contracts and numerical stability.References
W. M. Wonham, “On pole assignment in multi-input controllable linear systems,” IEEE Trans. Autom. Control 12 (1967) 660-665.
Examples
>>> state_feedback_poles([[0, 1], [0, 0]], [[0], [1]], [-1, -2]).round(6).tolist() [[2.0, 3.0]]
- engineeringkit.control_systems.state_matrices(A, B=None, C=None)[source]#
Validate and convert state-space matrices to float arrays.
- Parameters:
A (array_like) – Square state matrix, shape
(n, n)[1/s].B (array_like, optional) – Input matrix, shape
(n, m).C (array_like, optional) – Output matrix, shape
(p, n).
- Returns:
(A, B, C), withNonefor matrices not supplied.- Return type:
tuple of ndarray
- Raises:
ValueError – If a matrix is nonfinite, empty or has an inconsistent shape.
Examples
>>> A, B, C = state_matrices([[0, 1], [-2, -3]], B=[[0], [1]]) >>> A.shape, B.shape, C ((2, 2), (2, 1), None)
- engineeringkit.control_systems.state_space(A, B, C, D)[source]#
Continuous-time state-space model \(\dot x = Ax + Bu,\ y = Cx + Du\).
- Parameters:
A (array_like) – State matrix, shape
(n, n)[1/s].B (array_like) – Input matrix, shape
(n, m).C (array_like) – Output matrix, shape
(p, n).D (array_like) – Feedthrough matrix, shape
(p, m).
- Returns:
Continuous-time model.
- Return type:
scipy.signal.StateSpace
Notes
Matrix units follow the chosen state, input and output units; see Control systems: matrix contracts and numerical stability.
Examples
>>> sys = state_space([[-2]], [[2]], [[1]], [[0]]) >>> float((-sys.C @ np.linalg.solve(sys.A, sys.B) + sys.D).item()) 1.0
- engineeringkit.control_systems.step_response(sys, T=None)[source]#
Response to a unit step input from rest.
- Parameters:
sys (scipy.signal.lti) – Continuous-time model.
T (array_like, optional) – Time samples [s]; chosen automatically if omitted.
- Returns:
(t, y): time [s] and output per unit step.- Return type:
tuple of ndarray
Notes
Wraps
scipy.signal.step(). See Control systems: matrix contracts and numerical stability.Examples
>>> t, y = step_response(transfer_function([1], [1, 1]), [0.0, 1.0]) >>> round(float(y[-1]), 4) 0.6321
- engineeringkit.control_systems.transfer_function(num, den)[source]#
Continuous-time transfer function \(N(s)/D(s)\).
- Parameters:
num (array_like) – Numerator and denominator coefficients in descending powers of \(s\). The denominator must have degree at least zero and must not be identically zero.
den (array_like) – Numerator and denominator coefficients in descending powers of \(s\). The denominator must have degree at least zero and must not be identically zero.
- Returns:
Continuous-time SISO model.
- Return type:
scipy.signal.TransferFunction
Notes
Units of \(G\) are output units per input unit. See Control systems: matrix contracts and numerical stability.
Examples
>>> G = transfer_function([1], [1, 1]) >>> G.den.tolist() [1.0, 1.0]
- engineeringkit.control_systems.weight_matrix(matrix, size, name, strict=False)[source]#
Validate a symmetric positive (semi)definite weighting or covariance matrix.
- Parameters:
matrix (array_like) – Candidate matrix, shape
(size, size).size (int) – Required dimension.
name (str) – Argument name used in error messages.
strict (bool, optional) – Require positive definite (
True) rather than semidefinite.
- Returns:
The matrix as a float array.
- Return type:
ndarray
- Raises:
ValueError – If the matrix has the wrong shape, is not symmetric, or is indefinite.
Examples
>>> weight_matrix([[2.0]], 1, "R", strict=True) array([[2.]])
- engineeringkit.control_systems.zeros(sys)[source]#
Zeros of a SISO model, the roots of its numerator.
- Parameters:
sys (scipy.signal.lti) – SISO continuous-time model.
- Returns:
Zeros [1/s].
- Return type:
ndarray of complex
Notes
See Control systems: matrix contracts and numerical stability.
Examples
>>> zeros(transfer_function([2, 2], [1, 3, 2])).real.tolist() [-1.0]
- engineeringkit.control_systems.ziegler_nichols(ku, tu, controller='PID')[source]#
Ziegler-Nichols ultimate-sensitivity tuning.
Raise a proportional gain until the loop oscillates steadily: that gain is the ultimate gain \(K_u\) and the oscillation period is \(T_u\). The classic rules then set
type
\(K_p\)
\(T_i\)
\(T_d\)
P
\(0.5K_u\)
PI
\(0.45K_u\)
\(T_u/1.2\)
PID
\(0.6K_u\)
\(T_u/2\)
\(T_u/8\)
with \(K_i = K_p/T_i\) and \(K_d = K_p T_d\).
- Parameters:
ku (float) – Ultimate proportional gain [input/error], positive.
tu (float) – Ultimate oscillation period [s], positive.
controller ({"P", "PI", "PID"}, optional) – Controller structure.
- Returns:
Tuned controller.
- Return type:
Notes
The rules aim for roughly quarter-amplitude decay and are only a starting point; they give large overshoot on many plants. See Control systems: matrix contracts and numerical stability.
References
J. G. Ziegler and N. B. Nichols, “Optimum settings for automatic controllers,” Trans. ASME 64 (1942) 759-768.
Examples
>>> ziegler_nichols(8.0, 4.0) PID(kp=4.8, ki=2.4, kd=2.4)