Getting Started with ctrlpy¶
This guide walks you through modeling Linear Time-Invariant (LTI) systems, connecting blocks in series/parallel/feedback, simulating transient dynamics, computing frequency responses and stability margins, and visualizing results.
1. Creating LTI Models¶
ctrlpy supports continuous-time Transfer Functions and State-Space representations.
Transfer Functions (TransferFunction, tf)¶
A single-input single-output (SISO) transfer function:
import ctrlpy as cp
# G(s) = (2s + 5) / (s^2 + 3s + 2)
G = cp.tf([2, 5], [1, 3, 2])
print("Transfer Function:")
print(G)
print(f"Poles: {G.poles()}")
print(f"Zeros: {G.zeros()}")
State-Space Models (StateSpace, ss)¶
Continuous-time state-space systems:
import numpy as np
import ctrlpy as cp
A = [[-3.0, -2.0], [1.0, 0.0]]
B = [[1.0], [0.0]]
C = [[0.0, 10.0]]
D = [[0.0]]
sys = cp.ss(A, B, C, D)
print("State Space Model:")
print(sys)
Model Conversions¶
# Convert TransferFunction to StateSpace
sys_ss = G.to_ss()
# Convert StateSpace to TransferFunction
sys_tf = sys.to_tf()
2. Block Diagram Algebra & Interconnections¶
Combine systems using standard arithmetic operators (+, -, *, /) or dedicated functions (series, parallel, feedback):
import ctrlpy as cp
G1 = cp.tf([1], [1, 2])
G2 = cp.tf([3], [1, 4])
# Series (Cascade) Connection: G_series = G2 * G1
G_ser = cp.series(G1, G2)
# Parallel Connection: G_parallel = G1 + G2
G_par = cp.parallel(G1, G2)
# Closed-Loop Feedback Connection: T = G1 / (1 + G1 * G2)
T_closed = cp.feedback(G1, G2, sign=-1)
3. Time-Domain Simulations & Metric Extraction¶
Simulate step, impulse, and forced responses:
import ctrlpy as cp
# Define closed-loop system
T = cp.tf([4], [1, 2, 4])
# Step response
resp = cp.step_response(T, T=8.0)
print(f"Steady-State Value : {resp.steady_state_value():.4f}")
print(f"Rise Time (10%-90%): {resp.rise_time():.4f} s")
print(f"Settling Time (2%) : {resp.settling_time(tolerance=0.02):.4f} s")
print(f"Percent Overshoot : {resp.overshoot():.2f} %")
print(f"Peak Time : {resp.peak_time():.4f} s")
4. Frequency-Domain & Stability Margins¶
Compute Bode, Nyquist, and Root Locus responses:
import ctrlpy as cp
# Open-loop system
L = cp.tf([4], [1, 3, 2, 0])
# Exact stability margins
sm = cp.margin(L)
print(f"Gain Margin (GM) : {sm.gm_db:.2f} dB")
print(f"Phase Margin (PM) : {sm.pm_deg:.2f}°")
print(f"Gain Crossover Freq (wcg) : {sm.wcg:.4f} rad/s")
print(f"Phase Crossover Freq (wcp) : {sm.wcp:.4f} rad/s")
5. Dual-Backend Visualizations¶
ctrlpy provides dual visualization backends: static Matplotlib plots and interactive Plotly charts.
Static Plotting (Matplotlib)¶
import matplotlib.pyplot as plt
import ctrlpy as cp
L = cp.tf([10], [1, 3, 2, 0])
T = cp.feedback(L, 1.0)
fig1, ax1 = cp.plot_step(T)
fig2, (ax_mag, ax_phase) = cp.plot_bode(L, margins=True)
fig3, ax3 = cp.plot_nyquist(L)
fig4, ax4 = cp.plot_root_locus(L)
plt.show()
Interactive Visualizations (Plotly)¶
import ctrlpy as cp
L = cp.tf([10], [1, 3, 2, 0])
T = cp.feedback(L, 1.0)
fig_step = cp.iplot_step(T)
fig_bode = cp.iplot_bode(L, margins=True)
fig_nyquist = cp.iplot_nyquist(L)
fig_rlocus = cp.iplot_root_locus(L)
# Display interactive figures
fig_step.show()
6. Pedagogical & Educational Derivations (ctrlpy.pedagogy)¶
When ctrlpy[symbolic] is installed, access step-by-step mathematical derivations:
import ctrlpy as cp
from ctrlpy.pedagogy import root_locus_rules, routh_table, steady_state_analysis
G = cp.tf([1], [1, 3, 2, 0])
# 1. Routh-Hurwitz Stability Criterion & Gain Margin Range
routh_res = routh_table(G, k_symbol="K")
print(routh_res)
print(f"Stable K Range: {routh_res.k_range}")
# 2. Analytical Evans Root Locus Rules
rl_res = root_locus_rules(G)
print(rl_res)
# 3. System Type & Steady-State Tracking Errors
ss_res = steady_state_analysis(G)
print(ss_res)