| name | control-systems |
| description | Control systems fundamentals including PID control, state-space analysis, stability criteria, observer design, and robust control |
| license | MIT |
| compatibility | opencode |
| metadata | {"audience":"engineers","category":"engineering"} |
What I do
- Design and analyze control systems using various methods
- Implement PID and advanced control algorithms
- Perform stability and robustness analysis
- Design state observers and estimators
- Develop MIMO control strategies
- Tune controller parameters for optimal performance
- Analyze system frequency response
- Implement adaptive and nonlinear control
When to use me
When designing feedback control systems, analyzing stability, tuning controllers, or implementing advanced control strategies for dynamic systems.
Core Concepts
- Transfer function and state-space representation
- PID control and tuning methods
- Stability analysis (Routh-Hurwitz, Nyquist, Bode)
- Controllability and observability
- State feedback and LQR control
- Observer design (Luenberger, Kalman)
- Frequency response analysis
- Robust control (H-infinity, mu-synthesis)
- Adaptive control
- Nonlinear control systems
Code Examples
Transfer Function Analysis
import numpy as np
from dataclasses import dataclass
from typing import List, Tuple
import control
import matplotlib.pyplot as plt
@dataclass
class TransferFunction:
num: List[float]
den: List[float]
def eval(self, s: complex) -> complex:
"""Evaluate transfer function at s."""
num_val = sum(self.num[i] * s**(len(self.num) - 1 - i)
for i in range(len(self.num)))
den_val = sum(self.den[i] * s**(len(self.den) - 1 - i)
for i in range(len(self.den)))
return num_val / den_val if den_val != 0 else complex(float('inf'))
def () -> np.ndarray:
sys = control.TransferFunction(tf.num, tf.den)
t_out, y = control.step(sys, t)
y
() -> np.ndarray:
sys = control.TransferFunction(tf.num, tf.den)
t_out, y = control.impulse(sys, t)
y
() -> [np.ndarray, np.ndarray]:
sys = control.TransferFunction(tf.num, tf.den)
w_out, mag, phase = control.bode_plot(sys, w, plot=)
mag, phase
() -> [np.ndarray, np.ndarray]:
sys = control.TransferFunction(tf.num, tf.den)
k_out, poles = control.root_locus(sys, k, plot=)
k_out, poles
() -> [[[]], ]:
n = (coeffs) -
array = []
row1 = coeffs[::] (coeffs) % coeffs[::] + []
row2 = coeffs[::] (coeffs) % coeffs[::]
array.append(row1)
array.append(row2)
i (, n + ):
row = []
j ((row1) - ):
a = array[i-][]
row.append((array[i-][j+] * row1[] - array[i-][] * array[i-][j+]) / a)
array.append(row)
i == n:
sign_changes =
i ((array[])):
i == :
array[][i-] * array[][i] < :
sign_changes +=
stable = (x > x array[])
array, stable
sys_tf = TransferFunction([], [, , ])
t = np.linspace(, , )
y = step_response(sys_tf, t)
()
()
()
PID Controller Design
@dataclass
class PIDGains:
Kp: float
Ki: float
Kd: float
Tf: float = 0.0
def pid_transfer_function(gains: PIDGains) -> Tuple[List[float], List[float]]:
"""Get PID transfer function coefficients."""
if gains.Tf > 0:
num = [gains.Kp + gains.Ki * gains.Tf + gains.Kd,
gains.Ki + gains.Kd / gains.Tf,
gains.Kd * gains.Ki / gains.Tf]
den = [gains.Tf, 1, 0]
else:
num = [gains.Kp + gains.Kd, gains.Ki]
den = [1, 0]
return num, den
def ziegler_nichols_tuning(
Ku: float,
Tu: float
) -> PIDGains:
"""Ziegler-Nichols tuning method."""
return PIDGains(
Kp=0.6 * Ku,
Ki=1.2 * Ku / Tu,
Kd=0.075 * Ku * Tu
)
def cohen_coon_tuning(
K: float,
L: float,
T: float
) -> PIDGains:
"""Cohen-Coon tuning method."""
return PIDGains(
Kp=(1.35 * T / (K * L)) * ( + * (L / T)),
Ki= / L,
Kd= * T
)
() -> PIDGains:
Kc = tau / (K * (lambda_c + tau))
tau_I = tau
tau_D = lambda_c * tau / (tau + lambda_c)
PIDGains(Kp=Kc, Ki=Kc/tau_I, Kd=Kc*tau_D)
() -> [, , ]:
Kp, Ki, Kd
gains = ziegler_nichols_tuning(Ku=, Tu=)
()
imc_gains = imc_tuning(K=, tau=, lambda_c=)
()
State-Space Control
@dataclass
class StateSpaceModel:
A: np.ndarray
B: np.ndarray
C: np.ndarray
D: np.ndarray
def controllability_matrix(sys: StateSpaceModel) -> np.ndarray:
"""Check controllability."""
n = sys.A.shape[0]
Cc = sys.B.copy()
for i in range(1, n):
Cc = np.hstack([Cc, np.linalg.matrix_power(sys.A, i) @ sys.B])
return Cc
def observability_matrix(sys: StateSpaceModel) -> np.ndarray:
"""Check observability."""
n = sys.A.shape[0]
Co = sys.C.copy()
for i in range(1, n):
Co = np.vstack([Co, sys.C @ np.linalg.matrix_power(sys.A, i)])
return Co
def lyapunov_equation(A: np.ndarray, Q: np.ndarray) -> np.ndarray:
"""Solve Lyapunov equation A'P + PA = -Q."""
n = A.shape[0]
P = np.zeros((n, n))
from scipy.linalg import lyapunov_solve
return lyapunov_solve(A.T, -Q)
def lqr_design(
A: np.ndarray,
B: np.ndarray,
Q: np.ndarray,
R: float
) -> Tuple[np.ndarray, np.ndarray]:
"""Design LQR state feedback controller."""
from scipy.linalg import solve
n = A.shape[0]
P = solve_continuous_lyapunov(A.T @ Q, -Q @ B)
K = np.linalg.inv(R) @ B.T @ P
K, P
() -> np.ndarray:
control.place(A, B, desired_poles)
() -> [np.ndarray, np.ndarray]:
scipy.linalg solve_discrete_lyapunov
P = solve_discrete_lyapunov(A.T, C.T @ R @ C + Q)
L = P @ C.T @ np.linalg.inv(C @ P @ C.T + R)
L, P
A = np.array([[, ], [-, -]])
B = np.array([[], []])
C = np.array([[, ]])
D = np.array([[]])
sys_ss = StateSpaceModel(A, B, C, D)
Cc = controllability_matrix(sys_ss)
rank = np.linalg.matrix_rank(Cc)
()
Frequency Response Analysis
def gain_margin_phase_margin(
sys: control.TransferFunction
) -> Tuple[float, float]:
"""Calculate gain and phase margins."""
w, mag, phase = control.bode_plot(sys, plot=False)
phase_deg = np.rad2deg(phase)
idx_phase = np.where(phase_deg[:-1] * phase_deg[1:] < 0)[0]
if len(idx_phase) > 0:
w_pc = w[idx_phase[0]]
gm = 1 / np.abs(mag[idx_phase[0]])
else:
w_pc, gm = 0, float('inf')
mag_db = 20 * np.log10(mag)
idx_gain = np.where(mag_db[:-1] * mag_db[1:] < 0)[0]
if len(idx_gain) > 0:
w_gc = w[idx_gain[0]]
pm = 180 + phase_deg[idx_gain[0]]
else:
w_gc, pm = 0, float('inf')
return gm, pm
def nyquist_stability(sys: control.TransferFunction) -> Tuple[int, bool]:
"""Analyze Nyquist plot for stability."""
control nyquist_plot
s = * np.logspace(-, , )
sys_val = np.array([sys((s_i)) s_i s])
N = -np.(np.diff(np.angle(sys_val)) < -np.pi) -
P =
Z = N + P
N, Z ==
():
Adaptive and Robust Control
def model_reference_adaptive_control(
ref_model: np.ndarray,
plant: np.ndarray,
gamma: float = 100
) -> Tuple[np.ndarray, np.ndarray]:
"""MRAC parameter adaptation."""
pass
def sliding_mode_control(
x: np.ndarray,
A: np.ndarray,
B: np.ndarray,
K: np.ndarray,
lambda_smc: float,
eta: float
) -> np.ndarray:
"""Sliding mode control with reaching law."""
s = K @ x
sat_s = np.sign(s)
return np.linalg.inv(B) @ (A @ x + lambda_smc * sat_s + eta * sat_s)
def h_infinity_design(
G: control.TransferFunction,
W1: control.TransferFunction,
W2: control.TransferFunction,
gamma: float = 1.0
) -> control.TransferFunction:
"""H-infinity controller synthesis."""
pass
A_cl = np.array([[0, 1], [-2, -3]])
B = np.array([[0], [1]])
K = np.array([[-3, -2]])
lambda_smc = 10
eta = 2.0
x = np.array([[0.5], [0.2]])
u = sliding_mode_control(x, A_cl, B, K, lambda_smc, eta)
print(f"SMC input: {u[0,0]:.3f}")
Best Practices
- Always verify stability before deploying controllers
- Use anti-windup techniques for integral control
- Consider measurement noise in derivative terms
- Validate models with experimental data
- Use multiple tuning methods and compare results
- Consider robustness to parameter variations
- Implement safety limits and saturation handling
- Use proper signal conditioning (filtering, scaling)
- Document controller design and tuning procedures
- Test under realistic conditions before deployment