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)