or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

arma-methods.mdcorrelation-signal.mdeigenvalue-methods.mdindex.mdlinear-prediction.mdmath-tools.mdnonparametric-methods.mdparametric-ar.mdwindow-functions.md

correlation-signal.mddocs/

0

# Correlation and Signal Processing

1

2

Correlation analysis, signal generation, and essential signal processing utilities for spectral analysis applications. These functions provide the fundamental building blocks for many spectral estimation methods and signal analysis tasks.

3

4

## Capabilities

5

6

### Correlation Functions

7

8

Computation of auto-correlation and cross-correlation functions with various normalization options.

9

10

```python { .api }

11

def CORRELATION(x, y=None, maxlags=None, norm='unbiased'):

12

"""

13

Correlation function computation (auto or cross-correlation).

14

15

Parameters:

16

- x: array-like, first input signal

17

- y: array-like, second input signal (None for autocorrelation)

18

- maxlags: int, maximum lag to compute (None for full range)

19

- norm: str, normalization type ('biased', 'unbiased', 'coeff', None)

20

21

Returns:

22

array: Correlation values for positive lags only

23

"""

24

25

def xcorr(x, y=None, maxlags=None, norm='biased'):

26

"""

27

Fast cross-correlation using numpy.correlate.

28

29

Parameters:

30

- x: array-like, first input signal

31

- y: array-like, second input signal (None for autocorrelation)

32

- maxlags: int, maximum lag to compute

33

- norm: str, normalization type ('biased', 'unbiased', 'coeff', None)

34

35

Returns:

36

tuple: (correlation sequence array, lags array)

37

"""

38

```

39

40

### Signal Generation

41

42

Functions for generating test signals, waveforms, and synthetic data for spectral analysis.

43

44

```python { .api }

45

def chirp(t, f0=0., t1=1., f1=100., form='linear', phase=0):

46

"""

47

Generate chirp (frequency-swept) signal.

48

49

Parameters:

50

- t: array-like, time vector

51

- f0: float, starting frequency

52

- t1: float, end time for frequency sweep

53

- f1: float, ending frequency

54

- form: str, sweep type ('linear', 'quadratic', 'logarithmic', 'hyperbolic')

55

- phase: float, initial phase in radians

56

57

Returns:

58

array: Generated chirp signal

59

"""

60

61

def morlet(lb, ub, n):

62

"""

63

Generate Morlet wavelet for time-frequency analysis.

64

65

Parameters:

66

- lb: float, lower bound of time interval

67

- ub: float, upper bound of time interval

68

- n: int, number of samples

69

70

Returns:

71

array: Morlet wavelet samples

72

"""

73

74

def mexican(lb, ub, n):

75

"""

76

Generate Mexican hat wavelet (second derivative of Gaussian).

77

78

Parameters:

79

- lb: float, lower bound of time interval

80

- ub: float, upper bound of time interval

81

- n: int, number of samples

82

83

Returns:

84

array: Mexican hat wavelet samples

85

"""

86

87

def meyeraux(x):

88

"""

89

Meyer wavelet auxiliary function.

90

91

Parameters:

92

- x: array-like, input values

93

94

Returns:

95

array: Meyer auxiliary function values

96

"""

97

```

98

99

### Power and Decibel Conversions

100

101

Essential conversion functions between linear power, magnitude, and decibel scales.

102

103

```python { .api }

104

def pow2db(x):

105

"""

106

Convert power values to decibels.

107

108

Parameters:

109

- x: array-like, power values (linear scale)

110

111

Returns:

112

array: Power in decibels (10*log10(x))

113

"""

114

115

def db2pow(xdb):

116

"""

117

Convert decibel values to linear power.

118

119

Parameters:

120

- xdb: array-like, power values in dB

121

122

Returns:

123

array: Linear power values (10^(xdb/10))

124

"""

125

126

def mag2db(x):

127

"""

128

Convert magnitude values to decibels.

129

130

Parameters:

131

- x: array-like, magnitude values

132

133

Returns:

134

array: Magnitude in decibels (20*log10(x))

135

"""

136

137

def db2mag(xdb):

138

"""

139

Convert decibel values to linear magnitude.

140

141

Parameters:

142

- xdb: array-like, magnitude values in dB

143

144

Returns:

145

array: Linear magnitude values (10^(xdb/20))

146

"""

147

```

148

149

### Utility Functions

150

151

Mathematical and signal processing utility functions for various applications.

152

153

```python { .api }

154

def nextpow2(x):

155

"""

156

Find the next power of 2 greater than or equal to x.

157

158

Parameters:

159

- x: float or int, input value

160

161

Returns:

162

int: Next power of 2

163

"""

164

165

def fftshift(x):

166

"""

167

Wrapper to numpy.fft.fftshift for frequency domain centering.

168

169

Parameters:

170

- x: array-like, input array

171

172

Returns:

173

array: Shifted array with zero frequency at center

174

"""

175

176

def cshift(data, offset):

177

"""

178

Circular shift of array elements.

179

180

Parameters:

181

- data: array-like, input data array

182

- offset: int, number of positions to shift

183

184

Returns:

185

array: Circularly shifted array

186

"""

187

```

188

189

### Data Format Conversions

190

191

Functions for converting between different PSD representation formats.

192

193

```python { .api }

194

def twosided_2_onesided(data):

195

"""

196

Convert two-sided PSD to one-sided PSD.

197

198

Parameters:

199

- data: array-like, two-sided PSD values

200

201

Returns:

202

array: One-sided PSD values (positive frequencies only)

203

"""

204

205

def onesided_2_twosided(data):

206

"""

207

Convert one-sided PSD to two-sided PSD.

208

209

Parameters:

210

- data: array-like, one-sided PSD values

211

212

Returns:

213

array: Two-sided PSD values (negative and positive frequencies)

214

"""

215

216

def twosided_2_centerdc(data):

217

"""

218

Convert two-sided PSD to center-DC format.

219

220

Parameters:

221

- data: array-like, two-sided PSD values

222

223

Returns:

224

array: Center-DC format PSD (DC at center)

225

"""

226

227

def centerdc_2_twosided(data):

228

"""

229

Convert center-DC format PSD to standard two-sided format.

230

231

Parameters:

232

- data: array-like, center-DC format PSD

233

234

Returns:

235

array: Two-sided PSD values

236

"""

237

```

238

239

## Usage Examples

240

241

### Correlation Analysis

242

243

```python

244

import spectrum

245

import numpy as np

246

import matplotlib.pyplot as plt

247

248

# Generate test signals for correlation analysis

249

N = 200

250

n = np.arange(N)

251

252

# Signal 1: Sinusoid + noise

253

f1 = 0.1

254

signal1 = np.sin(2*np.pi*f1*n) + 0.3*np.random.randn(N)

255

256

# Signal 2: Delayed and noisy version of signal1

257

delay = 20

258

signal2 = np.zeros(N)

259

signal2[delay:] = signal1[:-delay] + 0.2*np.random.randn(N-delay)

260

261

plt.figure(figsize=(15, 12))

262

263

# Plot signals

264

plt.subplot(3, 2, 1)

265

plt.plot(signal1, 'b-', label='Signal 1', alpha=0.8)

266

plt.plot(signal2, 'r-', label='Signal 2 (delayed)', alpha=0.8)

267

plt.title('Input Signals')

268

plt.xlabel('Sample')

269

plt.ylabel('Amplitude')

270

plt.legend()

271

plt.grid(True)

272

273

# Autocorrelation of signal 1

274

plt.subplot(3, 2, 2)

275

maxlags = 50

276

autocorr1 = spectrum.CORRELATION(signal1, maxlags=maxlags, norm='unbiased')

277

lags_pos = np.arange(maxlags+1)

278

plt.plot(lags_pos, autocorr1, 'b-', linewidth=2)

279

plt.title('Autocorrelation of Signal 1')

280

plt.xlabel('Lag')

281

plt.ylabel('Correlation')

282

plt.grid(True)

283

284

# Cross-correlation using CORRELATION function

285

plt.subplot(3, 2, 3)

286

crosscorr = spectrum.CORRELATION(signal1, signal2, maxlags=maxlags, norm='unbiased')

287

plt.plot(lags_pos, crosscorr, 'g-', linewidth=2)

288

plt.title('Cross-correlation (CORRELATION function)')

289

plt.xlabel('Lag')

290

plt.ylabel('Correlation')

291

plt.grid(True)

292

293

# Cross-correlation using xcorr function (returns both positive and negative lags)

294

plt.subplot(3, 2, 4)

295

crosscorr_full, lags_full = spectrum.xcorr(signal1, signal2, maxlags=maxlags, norm='unbiased')

296

plt.plot(lags_full, crosscorr_full, 'r-', linewidth=2)

297

plt.axvline(delay, color='k', linestyle='--', alpha=0.7, label=f'True delay={delay}')

298

peak_lag = lags_full[np.argmax(crosscorr_full)]

299

plt.axvline(peak_lag, color='g', linestyle='--', alpha=0.7, label=f'Detected delay={peak_lag}')

300

plt.title('Cross-correlation (xcorr function)')

301

plt.xlabel('Lag')

302

plt.ylabel('Correlation')

303

plt.legend()

304

plt.grid(True)

305

306

# Compare different normalization methods

307

plt.subplot(3, 2, 5)

308

norms = ['biased', 'unbiased', 'coeff', None]

309

for norm_type in norms:

310

if norm_type is not None:

311

corr_norm = spectrum.CORRELATION(signal1, maxlags=30, norm=norm_type)

312

label = f'{norm_type}'

313

else:

314

corr_norm = spectrum.CORRELATION(signal1, maxlags=30, norm=None)

315

label = 'unnormalized'

316

317

lags_norm = np.arange(len(corr_norm))

318

plt.plot(lags_norm, corr_norm, linewidth=2, label=label)

319

320

plt.title('Autocorrelation: Different Normalizations')

321

plt.xlabel('Lag')

322

plt.ylabel('Correlation')

323

plt.legend()

324

plt.grid(True)

325

326

# Practical application: Time delay estimation

327

plt.subplot(3, 2, 6)

328

delays_to_test = range(5, 35, 5)

329

detected_delays = []

330

correlation_peaks = []

331

332

for test_delay in delays_to_test:

333

# Create delayed version

334

sig2_test = np.zeros(N)

335

sig2_test[test_delay:] = signal1[:-test_delay] + 0.1*np.random.randn(N-test_delay)

336

337

# Find peak in cross-correlation

338

cc, lags = spectrum.xcorr(signal1, sig2_test, maxlags=maxlags)

339

peak_idx = np.argmax(cc)

340

detected_delay = lags[peak_idx]

341

peak_value = cc[peak_idx]

342

343

detected_delays.append(detected_delay)

344

correlation_peaks.append(peak_value)

345

346

plt.plot(delays_to_test, detected_delays, 'bo-', linewidth=2, label='Detected')

347

plt.plot(delays_to_test, delays_to_test, 'r--', linewidth=2, label='True delay')

348

plt.title('Time Delay Estimation Accuracy')

349

plt.xlabel('True Delay (samples)')

350

plt.ylabel('Detected Delay (samples)')

351

plt.legend()

352

plt.grid(True)

353

354

plt.tight_layout()

355

plt.show()

356

357

# Print delay estimation results

358

print("Delay Estimation Results:")

359

print("-" * 30)

360

for true_del, det_del, peak in zip(delays_to_test, detected_delays, correlation_peaks):

361

error = abs(true_del - det_del)

362

print(f"True: {true_del:2d}, Detected: {det_del:2d}, Error: {error:2d}, Peak: {peak:.3f}")

363

```

364

365

### Signal Generation and Analysis

366

367

```python

368

import spectrum

369

import numpy as np

370

import matplotlib.pyplot as plt

371

372

# Time parameters

373

fs = 1000 # Sampling frequency

374

T = 2.0 # Duration

375

t = np.linspace(0, T, int(fs*T), False)

376

377

# Generate different types of signals

378

plt.figure(figsize=(15, 12))

379

380

# 1. Linear chirp

381

plt.subplot(3, 3, 1)

382

chirp_linear = spectrum.chirp(t, f0=50, t1=T, f1=200, form='linear')

383

plt.plot(t, chirp_linear)

384

plt.title('Linear Chirp (50-200 Hz)')

385

plt.xlabel('Time (s)')

386

plt.ylabel('Amplitude')

387

plt.grid(True)

388

389

# 2. Logarithmic chirp

390

plt.subplot(3, 3, 2)

391

chirp_log = spectrum.chirp(t, f0=50, t1=T, f1=200, form='logarithmic')

392

plt.plot(t, chirp_log)

393

plt.title('Logarithmic Chirp (50-200 Hz)')

394

plt.xlabel('Time (s)')

395

plt.ylabel('Amplitude')

396

plt.grid(True)

397

398

# 3. Quadratic chirp

399

plt.subplot(3, 3, 3)

400

chirp_quad = spectrum.chirp(t, f0=50, t1=T, f1=200, form='quadratic')

401

plt.plot(t, chirp_quad)

402

plt.title('Quadratic Chirp (50-200 Hz)')

403

plt.xlabel('Time (s)')

404

plt.ylabel('Amplitude')

405

plt.grid(True)

406

407

# 4. Morlet wavelet

408

plt.subplot(3, 3, 4)

409

morlet_wave = spectrum.morlet(-3, 3, 200)

410

t_morlet = np.linspace(-3, 3, 200)

411

plt.plot(t_morlet, morlet_wave, 'r-', linewidth=2)

412

plt.title('Morlet Wavelet')

413

plt.xlabel('Time')

414

plt.ylabel('Amplitude')

415

plt.grid(True)

416

417

# 5. Mexican hat wavelet

418

plt.subplot(3, 3, 5)

419

mexican_wave = spectrum.mexican(-3, 3, 200)

420

plt.plot(t_morlet, mexican_wave, 'g-', linewidth=2)

421

plt.title('Mexican Hat Wavelet')

422

plt.xlabel('Time')

423

plt.ylabel('Amplitude')

424

plt.grid(True)

425

426

# Spectrograms of chirp signals

427

plt.subplot(3, 3, 7)

428

from scipy import signal

429

f_spec, t_spec, Sxx = signal.spectrogram(chirp_linear, fs, nperseg=256, noverlap=128)

430

plt.pcolormesh(t_spec, f_spec, 10*np.log10(Sxx), shading='gouraud')

431

plt.ylabel('Frequency (Hz)')

432

plt.xlabel('Time (s)')

433

plt.title('Linear Chirp Spectrogram')

434

plt.ylim(0, 300)

435

436

plt.subplot(3, 3, 8)

437

f_spec, t_spec, Sxx = signal.spectrogram(chirp_log, fs, nperseg=256, noverlap=128)

438

plt.pcolormesh(t_spec, f_spec, 10*np.log10(Sxx), shading='gouraud')

439

plt.ylabel('Frequency (Hz)')

440

plt.xlabel('Time (s)')

441

plt.title('Logarithmic Chirp Spectrogram')

442

plt.ylim(0, 300)

443

444

plt.subplot(3, 3, 9)

445

f_spec, t_spec, Sxx = signal.spectrogram(chirp_quad, fs, nperseg=256, noverlap=128)

446

plt.pcolormesh(t_spec, f_spec, 10*np.log10(Sxx), shading='gouraud')

447

plt.ylabel('Frequency (Hz)')

448

plt.xlabel('Time (s)')

449

plt.title('Quadratic Chirp Spectrogram')

450

plt.ylim(0, 300)

451

452

plt.tight_layout()

453

plt.show()

454

455

# Compare chirp types in frequency domain

456

plt.figure(figsize=(12, 8))

457

458

signals = {

459

'Linear': chirp_linear,

460

'Logarithmic': chirp_log,

461

'Quadratic': chirp_quad

462

}

463

464

for i, (chirp_type, chirp_signal) in enumerate(signals.items()):

465

plt.subplot(2, 2, i+1)

466

467

# Compute PSD using Welch method

468

f_psd, psd = signal.welch(chirp_signal, fs, nperseg=1024, noverlap=512)

469

plt.semilogy(f_psd, psd, linewidth=2)

470

plt.title(f'{chirp_type} Chirp - PSD')

471

plt.xlabel('Frequency (Hz)')

472

plt.ylabel('PSD')

473

plt.grid(True)

474

plt.xlim(0, 300)

475

476

# Wavelet frequency content

477

plt.subplot(2, 2, 4)

478

morlet_fft = np.fft.fft(morlet_wave, 1024)

479

freqs_wav = np.fft.fftfreq(1024, d=6/200) # Adjust for time span

480

plt.semilogy(np.fft.fftshift(freqs_wav), np.fft.fftshift(np.abs(morlet_fft)**2), 'r-', linewidth=2, label='Morlet')

481

482

mexican_fft = np.fft.fft(mexican_wave, 1024)

483

plt.semilogy(np.fft.fftshift(freqs_wav), np.fft.fftshift(np.abs(mexican_fft)**2), 'g-', linewidth=2, label='Mexican Hat')

484

plt.title('Wavelet Frequency Content')

485

plt.xlabel('Frequency')

486

plt.ylabel('Power')

487

plt.legend()

488

plt.grid(True)

489

plt.xlim(-2, 2)

490

491

plt.tight_layout()

492

plt.show()

493

```

494

495

### Power and Decibel Conversions

496

497

```python

498

import spectrum

499

import numpy as np

500

import matplotlib.pyplot as plt

501

502

# Generate test data with different dynamic ranges

503

linear_powers = np.logspace(-3, 3, 100) # 10^-3 to 10^3

504

linear_magnitudes = np.sqrt(linear_powers)

505

506

# Convert to dB scales

507

power_db = spectrum.pow2db(linear_powers)

508

magnitude_db = spectrum.mag2db(linear_magnitudes)

509

510

# Convert back to linear

511

power_recovered = spectrum.db2pow(power_db)

512

magnitude_recovered = spectrum.db2mag(magnitude_db)

513

514

plt.figure(figsize=(15, 10))

515

516

# Linear vs dB power scales

517

plt.subplot(2, 3, 1)

518

plt.loglog(linear_powers, linear_powers, 'b-', linewidth=2, label='Linear scale')

519

plt.xlabel('Input Power')

520

plt.ylabel('Power')

521

plt.title('Linear Power Scale')

522

plt.grid(True)

523

plt.legend()

524

525

plt.subplot(2, 3, 2)

526

plt.semilogx(linear_powers, power_db, 'r-', linewidth=2)

527

plt.xlabel('Linear Power')

528

plt.ylabel('Power (dB)')

529

plt.title('Power to dB Conversion')

530

plt.grid(True)

531

532

plt.subplot(2, 3, 3)

533

plt.plot(power_db, power_recovered, 'g-', linewidth=2)

534

plt.xlabel('Power (dB)')

535

plt.ylabel('Recovered Linear Power')

536

plt.title('dB to Power Conversion')

537

plt.grid(True)

538

539

# Magnitude conversions

540

plt.subplot(2, 3, 4)

541

plt.loglog(linear_magnitudes, linear_magnitudes, 'b-', linewidth=2, label='Linear scale')

542

plt.xlabel('Input Magnitude')

543

plt.ylabel('Magnitude')

544

plt.title('Linear Magnitude Scale')

545

plt.grid(True)

546

plt.legend()

547

548

plt.subplot(2, 3, 5)

549

plt.semilogx(linear_magnitudes, magnitude_db, 'r-', linewidth=2)

550

plt.xlabel('Linear Magnitude')

551

plt.ylabel('Magnitude (dB)')

552

plt.title('Magnitude to dB Conversion')

553

plt.grid(True)

554

555

plt.subplot(2, 3, 6)

556

plt.plot(magnitude_db, magnitude_recovered, 'g-', linewidth=2)

557

plt.xlabel('Magnitude (dB)')

558

plt.ylabel('Recovered Linear Magnitude')

559

plt.title('dB to Magnitude Conversion')

560

plt.grid(True)

561

562

plt.tight_layout()

563

plt.show()

564

565

# Demonstrate relationship between power and magnitude dB

566

print("Power vs Magnitude dB Relationship:")

567

print("-" * 40)

568

test_values = [0.1, 1.0, 10.0, 100.0]

569

570

for val in test_values:

571

pow_db = spectrum.pow2db(val)

572

mag_db = spectrum.mag2db(np.sqrt(val)) # magnitude = sqrt(power)

573

print(f"Power: {val:6.1f} -> {pow_db:6.1f} dB")

574

print(f" Mag: {np.sqrt(val):6.3f} -> {mag_db:6.1f} dB")

575

print(f" Ratio: {pow_db/mag_db:.1f} (should be 2.0)")

576

print()

577

578

# Practical example: Signal analysis with dB scaling

579

fs = 1000

580

t = np.linspace(0, 1, fs)

581

582

# Multi-tone signal with different amplitudes

583

signal = (2.0 * np.sin(2*np.pi*50*t) + # Strong component

584

0.5 * np.sin(2*np.pi*150*t) + # Medium component

585

0.1 * np.sin(2*np.pi*300*t) + # Weak component

586

0.02 * np.random.randn(fs)) # Noise

587

588

# Compute PSD and convert to dB

589

f, psd_linear = signal.welch(signal, fs, nperseg=256)

590

psd_db = spectrum.pow2db(psd_linear)

591

592

plt.figure(figsize=(12, 8))

593

594

plt.subplot(2, 2, 1)

595

plt.plot(t[:200], signal[:200])

596

plt.title('Multi-tone Signal')

597

plt.xlabel('Time (s)')

598

plt.ylabel('Amplitude')

599

plt.grid(True)

600

601

plt.subplot(2, 2, 2)

602

plt.plot(f, psd_linear)

603

plt.title('PSD - Linear Scale')

604

plt.xlabel('Frequency (Hz)')

605

plt.ylabel('PSD')

606

plt.grid(True)

607

608

plt.subplot(2, 2, 3)

609

plt.semilogy(f, psd_linear)

610

plt.title('PSD - Log Scale')

611

plt.xlabel('Frequency (Hz)')

612

plt.ylabel('PSD')

613

plt.grid(True)

614

615

plt.subplot(2, 2, 4)

616

plt.plot(f, psd_db)

617

plt.title('PSD - dB Scale')

618

plt.xlabel('Frequency (Hz)')

619

plt.ylabel('PSD (dB)')

620

plt.grid(True)

621

622

plt.tight_layout()

623

plt.show()

624

625

# Dynamic range comparison

626

linear_range = np.max(psd_linear) / np.min(psd_linear[psd_linear > 0])

627

db_range = np.max(psd_db) - np.min(psd_db[np.isfinite(psd_db)])

628

629

print(f"Dynamic Range Analysis:")

630

print(f"Linear scale: {linear_range:.1e} (ratio)")

631

print(f"dB scale: {db_range:.1f} dB")

632

print(f"Equivalent ratio: {spectrum.db2pow(db_range):.1e}")

633

```

634

635

### Utility Functions and Data Format Conversions

636

637

```python

638

import spectrum

639

import numpy as np

640

import matplotlib.pyplot as plt

641

642

# Demonstrate utility functions

643

print("Utility Functions Demonstration:")

644

print("-" * 35)

645

646

# Next power of 2

647

test_values = [100, 255, 256, 513, 1000, 1024]

648

for val in test_values:

649

next_pow2 = spectrum.nextpow2(val)

650

print(f"nextpow2({val:4d}) = {next_pow2:4d} (2^{int(np.log2(next_pow2))})")

651

652

# Generate test PSD for format conversions

653

N = 512

654

freqs = np.linspace(0, 0.5, N//2 + 1) # One-sided frequencies

655

# Create artificial PSD with multiple peaks

656

psd_onesided = (np.exp(-(freqs-0.1)**2/0.01) +

657

0.5*np.exp(-(freqs-0.3)**2/0.005) +

658

0.1*np.ones_like(freqs)) # Noise floor

659

660

plt.figure(figsize=(15, 10))

661

662

# Original one-sided PSD

663

plt.subplot(2, 3, 1)

664

plt.plot(freqs, psd_onesided, 'b-', linewidth=2)

665

plt.title('Original One-sided PSD')

666

plt.xlabel('Normalized Frequency')

667

plt.ylabel('PSD')

668

plt.grid(True)

669

670

# Convert to two-sided

671

psd_twosided = spectrum.onesided_2_twosided(psd_onesided)

672

freqs_twosided = np.linspace(-0.5, 0.5, len(psd_twosided))

673

674

plt.subplot(2, 3, 2)

675

plt.plot(freqs_twosided, psd_twosided, 'r-', linewidth=2)

676

plt.title('Two-sided PSD')

677

plt.xlabel('Normalized Frequency')

678

plt.ylabel('PSD')

679

plt.grid(True)

680

681

# Convert two-sided to center-DC

682

psd_centerdc = spectrum.twosided_2_centerdc(psd_twosided)

683

684

plt.subplot(2, 3, 3)

685

plt.plot(freqs_twosided, psd_centerdc, 'g-', linewidth=2)

686

plt.title('Center-DC Format PSD')

687

plt.xlabel('Normalized Frequency')

688

plt.ylabel('PSD')

689

plt.grid(True)

690

691

# Convert back to two-sided

692

psd_twosided_recovered = spectrum.centerdc_2_twosided(psd_centerdc)

693

694

plt.subplot(2, 3, 4)

695

plt.plot(freqs_twosided, psd_twosided_recovered, 'm--', linewidth=2, label='Recovered')

696

plt.plot(freqs_twosided, psd_twosided, 'r:', alpha=0.7, label='Original')

697

plt.title('Recovered Two-sided PSD')

698

plt.xlabel('Normalized Frequency')

699

plt.ylabel('PSD')

700

plt.legend()

701

plt.grid(True)

702

703

# Convert back to one-sided

704

psd_onesided_recovered = spectrum.twosided_2_onesided(psd_twosided_recovered)

705

706

plt.subplot(2, 3, 5)

707

plt.plot(freqs, psd_onesided_recovered, 'c--', linewidth=2, label='Recovered')

708

plt.plot(freqs, psd_onesided, 'b:', alpha=0.7, label='Original')

709

plt.title('Recovered One-sided PSD')

710

plt.xlabel('Normalized Frequency')

711

plt.ylabel('PSD')

712

plt.legend()

713

plt.grid(True)

714

715

# Circular shift demonstration

716

plt.subplot(2, 3, 6)

717

test_signal = np.sin(2*np.pi*0.1*np.arange(100))

718

shifts = [0, 10, -15, 25]

719

for i, shift in enumerate(shifts):

720

shifted = spectrum.cshift(test_signal, shift)

721

plt.plot(shifted[:50], label=f'Shift {shift}', alpha=0.8)

722

plt.title('Circular Shift Examples')

723

plt.xlabel('Sample')

724

plt.ylabel('Amplitude')

725

plt.legend()

726

plt.grid(True)

727

728

plt.tight_layout()

729

plt.show()

730

731

# Verify conversion accuracy

732

print("\nFormat Conversion Accuracy:")

733

print("-" * 30)

734

error_onesided = np.max(np.abs(psd_onesided - psd_onesided_recovered))

735

error_twosided = np.max(np.abs(psd_twosided - psd_twosided_recovered))

736

737

print(f"One-sided round-trip error: {error_onesided:.2e}")

738

print(f"Two-sided round-trip error: {error_twosided:.2e}")

739

740

# Demonstrate FFT shift

741

fft_example = np.fft.fft(np.random.randn(16))

742

print("\nFFT Shift Example:")

743

print("Original FFT order:", np.arange(16))

744

print("After fftshift: ", np.arange(16)[np.fft.fftshift(np.arange(16)) == np.arange(16)])

745

746

# Show frequency ordering

747

N_fft = 16

748

original_freqs = np.fft.fftfreq(N_fft)

749

shifted_freqs = spectrum.fftshift(original_freqs)

750

751

print(f"Original freq bins: {original_freqs}")

752

print(f"Shifted freq bins: {shifted_freqs}")

753

```