01_signals_classification.py

Download
python 321 lines 10.9 KB
  1#!/usr/bin/env python3
  2"""
  3Signals and Systems - Basic Signal Types and Classification
  4
  5Demonstrates fundamental signal concepts:
  6- Basic signal types: sinusoidal, exponential, unit step, unit impulse,
  7  rectangular pulse
  8- Signal classification: continuous vs discrete, periodic vs aperiodic,
  9  deterministic vs random
 10- Even/odd decomposition of a signal
 11- Signal energy and power
 12- Time-shifting, scaling, and reversal operations
 13
 14Dependencies: numpy, matplotlib
 15"""
 16
 17import numpy as np
 18import matplotlib.pyplot as plt
 19
 20
 21def generate_basic_signals():
 22    """Generate and plot the five fundamental signal types."""
 23    print("=" * 60)
 24    print("BASIC SIGNAL TYPES")
 25    print("=" * 60)
 26
 27    t = np.linspace(-2, 4, 1000)   # Continuous-time axis
 28    n = np.arange(-10, 30)          # Discrete-time axis
 29
 30    # --- 1. Sinusoidal signal: x(t) = A * cos(2*pi*f*t + phi) ---
 31    A, f, phi = 1.5, 1.0, np.pi / 4
 32    x_sin = A * np.cos(2 * np.pi * f * t + phi)
 33    print(f"Sinusoidal: A={A}, f={f} Hz, phi={phi:.2f} rad")
 34    print(f"  Period T = 1/f = {1/f:.2f} s")
 35
 36    # --- 2. Decaying exponential: x(t) = e^(-alpha * t) * u(t) ---
 37    alpha = 1.2
 38    x_exp = np.exp(-alpha * t) * (t >= 0)
 39    print(f"\nDecaying exponential: alpha={alpha}")
 40    print(f"  Time constant tau = 1/alpha = {1/alpha:.2f} s")
 41
 42    # --- 3. Unit step function: u(t) = 1 if t >= 0, else 0 ---
 43    x_step = (t >= 0).astype(float)
 44    print("\nUnit step: u(t) = 1 for t >= 0, 0 otherwise")
 45
 46    # --- 4. Unit impulse (approximated as narrow pulse): delta(t) ---
 47    # The true Dirac delta is the limit of a rectangular pulse of width epsilon
 48    # and height 1/epsilon as epsilon -> 0. Here we approximate it.
 49    dt = t[1] - t[0]
 50    x_impulse = np.zeros_like(t)
 51    idx = np.argmin(np.abs(t))     # index closest to t=0
 52    x_impulse[idx] = 1.0 / dt     # area = 1 (sifting property)
 53    print("\nUnit impulse (approximated): delta(t), area = 1")
 54
 55    # --- 5. Rectangular pulse: rect(t) = 1 for |t| < T/2, else 0 ---
 56    T_rect = 1.0                   # pulse width
 57    x_rect = (np.abs(t) < T_rect / 2).astype(float)
 58    print(f"\nRectangular pulse: width={T_rect} s, centered at t=0")
 59
 60    # Plot
 61    fig, axes = plt.subplots(3, 2, figsize=(12, 10))
 62    fig.suptitle("Basic Signal Types", fontsize=14, fontweight='bold')
 63
 64    axes[0, 0].plot(t, x_sin, 'b')
 65    axes[0, 0].set_title(f"Sinusoidal: {A}·cos(2π·{f}·t + π/4)")
 66    axes[0, 0].set_xlabel("t (s)")
 67    axes[0, 0].axhline(0, color='k', linewidth=0.5)
 68
 69    axes[0, 1].plot(t, x_exp, 'r')
 70    axes[0, 1].set_title(f"Decaying Exponential: e^(-{alpha}t)·u(t)")
 71    axes[0, 1].set_xlabel("t (s)")
 72
 73    axes[1, 0].plot(t, x_step, 'g')
 74    axes[1, 0].set_title("Unit Step: u(t)")
 75    axes[1, 0].set_xlabel("t (s)")
 76    axes[1, 0].set_ylim(-0.2, 1.5)
 77
 78    axes[1, 1].plot(t, x_impulse, 'm')
 79    axes[1, 1].set_title("Unit Impulse: δ(t) [approximated]")
 80    axes[1, 1].set_xlabel("t (s)")
 81    axes[1, 1].set_xlim(-0.5, 0.5)
 82
 83    axes[2, 0].plot(t, x_rect, 'orange')
 84    axes[2, 0].set_title(f"Rectangular Pulse: width={T_rect} s")
 85    axes[2, 0].set_xlabel("t (s)")
 86    axes[2, 0].set_ylim(-0.2, 1.5)
 87
 88    # Discrete-time sinusoid alongside
 89    x_disc = np.cos(2 * np.pi * 0.1 * n)
 90    axes[2, 1].stem(n, x_disc, basefmt='k-', linefmt='C0-', markerfmt='C0o')
 91    axes[2, 1].set_title("Discrete-Time Sinusoid: cos(0.2π·n)")
 92    axes[2, 1].set_xlabel("n (samples)")
 93
 94    for ax in axes.flat:
 95        ax.grid(True, alpha=0.3)
 96        ax.set_ylabel("Amplitude")
 97
 98    plt.tight_layout()
 99    plt.show()
100
101
102def classify_signals():
103    """Illustrate signal classification categories."""
104    print("\n" + "=" * 60)
105    print("SIGNAL CLASSIFICATION")
106    print("=" * 60)
107
108    t = np.linspace(0, 4, 1000)
109    n = np.arange(0, 40)
110
111    # Periodic vs Aperiodic
112    # Periodic: x(t) = cos(2*pi*t), period T=1
113    x_periodic = np.cos(2 * np.pi * t)
114    # Aperiodic: decaying exponential has no repeating pattern
115    x_aperiodic = np.exp(-t)
116
117    # Deterministic vs Random
118    x_deterministic = np.sin(2 * np.pi * t)
119    rng = np.random.default_rng(42)
120    x_random = rng.standard_normal(len(t))   # White Gaussian noise
121
122    # Check periodicity numerically
123    T_candidate = 1.0          # Expected period
124    N_check = int(T_candidate / (t[1] - t[0]))
125    error_periodic = np.mean(np.abs(x_periodic[N_check:] - x_periodic[:-N_check]))
126    print(f"Periodic signal x(t)=cos(2πt):  period-shift error = {error_periodic:.6f}")
127    print(f"  -> {'Periodic' if error_periodic < 1e-6 else 'Aperiodic'}")
128
129    print("\nSignal classification summary:")
130    print("  Continuous-time: defined for all real t  (e.g. cos(2πt))")
131    print("  Discrete-time:   defined only at integer n  (e.g. cos(0.2πn))")
132    print("  Periodic:        x(t+T) = x(t) for all t")
133    print("  Aperiodic:       no such T exists")
134    print("  Deterministic:   future values are exactly predictable")
135    print("  Random:          future values described by probability")
136
137    fig, axes = plt.subplots(2, 2, figsize=(12, 7))
138    fig.suptitle("Signal Classification", fontsize=14, fontweight='bold')
139
140    axes[0, 0].plot(t, x_periodic, 'b')
141    axes[0, 0].set_title("Periodic: cos(2πt), T=1 s")
142    axes[0, 0].axvline(1, color='r', linestyle='--', alpha=0.5, label='T=1')
143    axes[0, 0].axvline(2, color='r', linestyle='--', alpha=0.5)
144    axes[0, 0].legend()
145
146    axes[0, 1].plot(t, x_aperiodic, 'r')
147    axes[0, 1].set_title("Aperiodic: e^(-t)")
148
149    axes[1, 0].plot(t, x_deterministic, 'g')
150    axes[1, 0].set_title("Deterministic: sin(2πt)")
151
152    axes[1, 1].plot(t, x_random, 'm', linewidth=0.5)
153    axes[1, 1].set_title("Random: White Gaussian Noise")
154
155    for ax in axes.flat:
156        ax.set_xlabel("t (s)")
157        ax.set_ylabel("Amplitude")
158        ax.grid(True, alpha=0.3)
159
160    plt.tight_layout()
161    plt.show()
162
163
164def even_odd_decomposition():
165    """
166    Decompose a signal into its even and odd components.
167
168    Any signal x(t) can be written as:
169        x(t) = x_e(t) + x_o(t)
170    where:
171        x_e(t) = [x(t) + x(-t)] / 2    (even part)
172        x_o(t) = [x(t) - x(-t)] / 2    (odd part)
173    """
174    print("\n" + "=" * 60)
175    print("EVEN/ODD DECOMPOSITION")
176    print("=" * 60)
177
178    t = np.linspace(-3, 3, 1000)
179    # Example signal: x(t) = e^t * u(t)  (one-sided exponential)
180    x = np.exp(t) * (t >= 0)
181
182    x_even = (x + x[::-1]) / 2   # x[::-1] is x(-t) for symmetric t axis
183    x_odd  = (x - x[::-1]) / 2
184
185    # Verify decomposition
186    error = np.max(np.abs(x - (x_even + x_odd)))
187    print(f"x(t) = e^t * u(t)")
188    print(f"Reconstruction error ||x - (x_e + x_o)||_inf = {error:.2e}")
189    print(f"Even part is symmetric:  x_e(t) = x_e(-t)")
190    print(f"Odd part is antisymmetric: x_o(t) = -x_o(-t)")
191
192    fig, axes = plt.subplots(1, 3, figsize=(13, 4))
193    fig.suptitle("Even/Odd Decomposition of x(t) = e^t·u(t)", fontsize=13)
194
195    axes[0].plot(t, x, 'b', linewidth=2)
196    axes[0].set_title("Original: x(t)")
197
198    axes[1].plot(t, x_even, 'r', linewidth=2)
199    axes[1].set_title("Even Part: x_e(t) = [x(t)+x(-t)]/2")
200    axes[1].axhline(0, color='k', linewidth=0.5)
201
202    axes[2].plot(t, x_odd, 'g', linewidth=2)
203    axes[2].set_title("Odd Part: x_o(t) = [x(t)-x(-t)]/2")
204    axes[2].axhline(0, color='k', linewidth=0.5)
205
206    for ax in axes:
207        ax.set_xlabel("t")
208        ax.set_ylabel("Amplitude")
209        ax.grid(True, alpha=0.3)
210        ax.axvline(0, color='k', linewidth=0.5)
211
212    plt.tight_layout()
213    plt.show()
214
215
216def energy_and_power():
217    """
218    Compute signal energy and power.
219
220    Energy:  E = integral |x(t)|^2 dt  (finite for energy signals)
221    Power:   P = lim(T->inf) (1/T) * integral_{-T/2}^{T/2} |x(t)|^2 dt
222               (finite for power signals)
223
224    - Energy signal: E < inf, P = 0  (e.g., decaying exponential)
225    - Power signal:  P < inf, E = inf (e.g., sinusoidal)
226    """
227    print("\n" + "=" * 60)
228    print("SIGNAL ENERGY AND POWER")
229    print("=" * 60)
230
231    dt = 0.001
232    t_long = np.arange(-50, 50, dt)
233
234    # Energy signal: x(t) = e^(-t) * u(t)
235    alpha = 1.0
236    x_energy = np.exp(-alpha * np.abs(t_long)) * (t_long >= 0)
237    E_numerical = np.sum(x_energy ** 2) * dt
238    E_analytical = 1 / (2 * alpha)   # integral of e^(-2*alpha*t), t>=0
239    print(f"Energy signal: x(t) = e^(-{alpha}t)·u(t)")
240    print(f"  Numerical energy  E = {E_numerical:.4f}")
241    print(f"  Analytical energy E = 1/(2α) = {E_analytical:.4f}")
242
243    # Power signal: x(t) = A * cos(2*pi*f*t)
244    A_pow, f_pow = 2.0, 1.0
245    x_power = A_pow * np.cos(2 * np.pi * f_pow * t_long)
246    T_obs = t_long[-1] - t_long[0]
247    P_numerical = np.sum(x_power ** 2) * dt / T_obs
248    P_analytical = A_pow ** 2 / 2     # average power of sinusoid
249    print(f"\nPower signal:  x(t) = {A_pow}·cos(2π·{f_pow}·t)")
250    print(f"  Numerical power   P = {P_numerical:.4f}")
251    print(f"  Analytical power  P = A²/2 = {P_analytical:.4f}")
252    print(f"  Energy of sinusoid is infinite (power signal)")
253
254
255def time_operations():
256    """
257    Demonstrate time-domain signal transformations:
258      - Time shifting:  x(t - t0)
259      - Time scaling:   x(a*t)    -- compression (a>1) or expansion (a<1)
260      - Time reversal:  x(-t)
261    """
262    print("\n" + "=" * 60)
263    print("TIME-DOMAIN OPERATIONS")
264    print("=" * 60)
265
266    t = np.linspace(-3, 5, 1000)
267    # Base signal: rectangular pulse centered at t=0, width=1
268    x = (np.abs(t) < 0.5).astype(float)
269
270    t0 = 1.5        # shift amount
271    a_compress = 2  # compression factor
272    a_expand = 0.5  # expansion factor
273
274    x_shifted  = (np.abs(t - t0) < 0.5).astype(float)    # delay by t0
275    x_compress = (np.abs(a_compress * t) < 0.5).astype(float)  # x(2t): half width
276    x_expand   = (np.abs(a_expand * t) < 0.5).astype(float)    # x(t/2): double width
277    x_reversed = (np.abs(-t) < 0.5).astype(float)              # x(-t)
278
279    print(f"Base: rect(t), width=1")
280    print(f"Time shift by {t0}:       x(t-{t0}) -> pulse centered at t={t0}")
281    print(f"Time compression (a={a_compress}): x({a_compress}t) -> pulse width = 1/{a_compress} = 0.5")
282    print(f"Time expansion   (a={a_expand}): x({a_expand}t) -> pulse width = 1/{a_expand} = 2.0")
283    print(f"Time reversal:            x(-t) -> pulse reflected about t=0")
284
285    fig, axes = plt.subplots(2, 3, figsize=(14, 7))
286    fig.suptitle("Time-Domain Signal Transformations", fontsize=14, fontweight='bold')
287
288    plots = [
289        (x,          "Original: x(t)"),
290        (x_shifted,  f"Time Shift: x(t-{t0})"),
291        (x_compress, f"Compression: x({a_compress}t)"),
292        (x_expand,   f"Expansion: x(t/{1//a_expand:.0f})"),
293        (x_reversed, "Reversal: x(-t)"),
294    ]
295
296    for idx, (sig, title) in enumerate(plots):
297        ax = axes[idx // 3][idx % 3]
298        ax.plot(t, sig, 'b', linewidth=2)
299        ax.set_title(title)
300        ax.set_xlabel("t")
301        ax.set_ylabel("Amplitude")
302        ax.set_ylim(-0.2, 1.5)
303        ax.grid(True, alpha=0.3)
304
305    # Hide unused subplot
306    axes[1][2].axis('off')
307    plt.tight_layout()
308    plt.show()
309
310
311if __name__ == "__main__":
312    generate_basic_signals()
313    classify_signals()
314    even_odd_decomposition()
315    energy_and_power()
316    time_operations()
317
318    print("\n" + "=" * 60)
319    print("All demonstrations completed!")
320    print("=" * 60)