1. 理論的背景:長方形波とSinc関数
時間領域での鋭い「長方形」の変化は、周波数領域では無限に広がる「Sinc関数」として現れます。これは、急峻な変化を再現するには非常に高い周波数成分が必要であることを示唆しています。
時間領域の定義
振幅 、パルス幅 の孤立パルス は次のように定義されます。
連続フーリエ変換 (CFT)
この信号をフーリエ変換すると、以下の結果が得られます。
工学的ポイント
- ピーク値: 周波数 のとき、振幅は (パルスの面積)になります。
- 零点 (Zero Crossings): の地点で振幅が 0 になります。つまり、パルス幅 が狭くなるほど、周波数領域での広がり(最初の零点までの距離)は大きくなります。
2. シミュレータの内部ロジック
コード内の TheoreticalModel クラスでは、以下の 2 つのアプローチを比較しています。
A. 理論値の算出
X_cont_mag では、上記の を直接計算しています。これはサンプリングの影響を受けない「理想的な形」です。
B. FFT(離散フーリエ変換)による近似
計算機で扱うため、信号をサンプリング周波数 でデジタル化しています。
-
np.fft.ifftshift: パルスを時間軸の「中心」に置いたまま正しく FFT にかけるための処理です。 - 正規化: FFT の結果を でスケールすることで、連続フーリエ変換の物理的な振幅次元に整合させています。
3. このシミュレータで見られる「工学的現象」
このツールを使って以下のパラメータを動かすことで、重要な現象を観察できます。
| 操作 | 観察される現象 | 工学的な意味 |
|---|---|---|
| ** を小さくする** | 周波数スペクトルが横に広がる | 不確定性原理: 時間的な制約が強い信号ほど、広い帯域幅を占有する。 |
| (データ点数)を増やす | 黒い点(FFT点)の密度が上がる | 周波数分解能: 観測時間を長くするほど、周波数を細かく分析できる。 |
| ** を小さくする** | 高域のスペクトルが折り返される | エイリアシング: サンプリング速度が足りないと、正しく変換できない。 |
4. ログとPDF保存の活用
- Ghost Line (グレーの線): 前回のシミュレーション結果が薄く残るため、「パルス幅を 2 倍にしたらスペクトルはどう変わるか?」といった感度解析が容易です。
-
PDF保存:
google.colab.files.downloadを用いて、レポートや設計資料にそのまま使えるベクトル形式の図表を即座に手元に保存できます。
# Program Name: Rectangular Pulse Fourier Transform Simulator
# Creation Date: 2026-01-07
# Purpose: Interactive simulation with PDF export capability
# ==========================================
# 1. README
# ==========================================
def print_documentation():
print("""
================================================================================
[README: 孤立パルス(長方形波)の連続・離散フーリエ変換シミュレータ]
1. 概要 (Overview):
このプログラムは、時間領域における孤立パルス(長方形波)のフーリエ変換をシミュレーションします。
理論的な連続フーリエ変換(CFT: Sinc関数)と、計算機による高速フーリエ変換(FFT)の結果を
比較・検証することが可能です。サンプリング定理や周波数分解能の理解を助けます。
2. 使い方 (Usage):
- [Model Parameters]: パルスの振幅 A と パルス幅 Tw を設定します。
- [Observed / Input Data]: サンプリング周波数 fs と データ点数 N を設定します。
- [Environment / Options]: 解析時間範囲などの環境設定を行います。
- "Run Simulation" ボタンで計算と描画を実行します。
- "Save PDF" ボタンで結果をPDFとして保存・ダウンロードします。
3. 支配方程式 (Governing Equations):
- 孤立パルスの定義 (Rectangular Pulse):
x(t) = A (|t| <= Tw/2), 0 (|t| > Tw/2)
- 連続フーリエ変換 (Continuous Fourier Transform):
X(f) = A * Tw * sinc(f * Tw)
ここで sinc(x) = sin(pi * x) / (pi * x) と定義します。
- 離散フーリエ変換 (Discrete Fourier Transform):
X[k] = sum_{n=0}^{N-1} x[n] * exp(-j * 2 * pi * n * k / N)
4. 変数定義 (Definitions):
- A: 振幅 (Amplitude)
- Tw: パルス幅 (Pulse Width [s])
- fs: サンプリング周波数 (Sampling Frequency [Hz])
- N: データ点数 (Number of Points)
- df: 周波数分解能 = fs / N (Frequency Resolution [Hz])
================================================================================
""")
# ==========================================
# 2. Setup Code
# ==========================================
# !pip install numpy matplotlib seaborn
from google.colab import files
import numpy as np
import matplotlib.pyplot as plt
import ipywidgets as widgets
from IPython.display import display, clear_output
import datetime
import time
# 再現性のためのシード設定
np.random.seed(42)
# ==========================================
# 3. TheoreticalModel
# ==========================================
class TheoreticalModel:
def __init__(self):
self.logs = []
self.ghost_data = None
def validate(self, params):
if params['fs'] <= 0:
print("警告: サンプリング周波数は0より大きい必要があります。 / Warning: fs must be > 0.")
return False
if params['Tw'] <= 0:
print("警告: パルス幅は0より大きい必要があります。 / Warning: Tw must be > 0.")
return False
if params['N'] < 10:
print("警告: データ点数が少なすぎます。 / Warning: N is too small.")
return False
return True
def sinc(self, x):
return np.sinc(x) # NumPy's sinc is sin(pi*x)/(pi*x)
def calculate(self, p):
# 時間軸の設定 (Time axis)
# FFTの可視化のためにパルスを中央に配置する
t_duration = p['N'] / p['fs']
t = np.linspace(-t_duration/2, t_duration/2, p['N'], endpoint=False)
# 1. 時間領域信号 (Time Domain Signal)
x = np.where(np.abs(t) <= p['Tw']/2, p['A'], 0.0)
# 2. 連続フーリエ変換(理論値) (CFT Theoretical)
# ナイキスト周波数まで計算
f_cont = np.linspace(-p['fs']/2, p['fs']/2, 1000)
X_cont_mag = np.abs(p['A'] * p['Tw'] * self.sinc(f_cont * p['Tw']))
# 3. 離散フーリエ変換 (FFT)
# fftを実行する際は、位相の整合性をとるためにfftshiftを使用
X_fft_raw = np.fft.fft(np.fft.ifftshift(x))
X_fft_mag = np.abs(np.fft.fftshift(X_fft_raw)) / p['fs'] # 積分近似のための正規化
f_fft = np.fft.fftshift(np.fft.fftfreq(p['N'], d=1/p['fs']))
results = {
't': t, 'x': x,
'f_cont': f_cont, 'X_cont_mag': X_cont_mag,
'f_fft': f_fft, 'X_fft_mag': X_fft_mag,
'peak_mag': p['A'] * p['Tw']
}
return results
def print_calculation_steps(self, p, res):
print("\n--- 理論計算プロセスの表示 (Calculation Steps) ---")
A, Tw = p['A'], p['Tw']
print(f"1. 連続フーリエ変換のピーク値 (CFT Peak Value):")
print(f" X(0) = A * Tw = {A} * {Tw} = {A * Tw}")
print(f"2. 第1零点周波数 (First Zero Frequency):")
print(f" f_zero = 1 / Tw = 1 / {Tw} = {1/Tw:.4f} [Hz]")
print(f"3. 周波数分解能 (Frequency Resolution):")
print(f" df = fs / N = {p['fs']} / {p['N']} = {p['fs']/p['N']:.4f} [Hz]")
print("--------------------------------------------------\n")
# ==========================================
# 4. Visualizer
# ==========================================
class Visualizer:
def __init__(self):
plt.style.use('seaborn-v0_8-muted')
def create_plot(self, current, ghost=None):
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))
# Plot 1: Time Domain
if ghost:
ax1.plot(ghost['t'], ghost['x'], color='gray', alpha=0.3, linestyle='--', label='Previous')
ax1.plot(current['t'], current['x'], color='blue', linewidth=2, label='Rectangular Pulse x(t)')
ax1.set_title('Time Domain: Isolated Rectangular Pulse', fontsize=12)
ax1.set_xlabel('Time [s]')
ax1.set_ylabel('Amplitude')
ax1.grid(True)
ax1.legend()
# Plot 2: Frequency Domain (Comparison)
if ghost:
ax2.plot(ghost['f_cont'], ghost['X_cont_mag'], color='gray', alpha=0.2, linestyle='--')
ax2.scatter(ghost['f_fft'], ghost['X_fft_mag'], color='gray', alpha=0.2, s=10)
ax2.plot(current['f_cont'], current['X_cont_mag'], color='red', linewidth=1.5, label='Theoretical CFT (Sinc)')
ax2.scatter(current['f_fft'], current['X_fft_mag'], color='black', s=15, label='Discrete FFT Points', zorder=3)
ax2.set_title('Frequency Domain: Magnitude Spectrum Comparison', fontsize=12)
ax2.set_xlabel('Frequency [Hz]')
ax2.set_ylabel('|X(f)| Magnitude')
ax2.grid(True)
ax2.legend()
plt.tight_layout()
return fig
# ==========================================
# 5. UIBuilder
# ==========================================
class UIBuilder:
def __init__(self, model, visualizer):
self.model = model
self.viz = visualizer
self.current_fig = None
# [Model Parameters]
self.A = widgets.FloatText(value=1.0, description='Amplitude A:')
self.Tw = widgets.FloatText(value=0.5, description='Width Tw [s]:')
# [Observed / Input Data]
self.fs = widgets.FloatText(value=10.0, description='S-Rate fs [Hz]:')
self.N = widgets.IntText(value=128, description='Points N:')
# [Environment / Options]
self.show_calc = widgets.Checkbox(value=True, description='Show Calculation Steps')
# Buttons
self.btn_run = widgets.Button(description="Run Simulation / 実行", button_style='primary')
self.btn_reset = widgets.Button(description="Reset / リセット", button_style='warning')
self.btn_save = widgets.Button(description="Save PDF / PDF保存", button_style='success')
self.btn_show_log = widgets.Button(description="Show Log / 履歴表示")
self.btn_clear_log = widgets.Button(description="Clear Log / 履歴消去")
self.output = widgets.Output()
# Layout
self.ui_layout = widgets.VBox([
widgets.Label(value="[Model Parameters]"),
widgets.HBox([self.A, self.Tw]),
widgets.Label(value="[Observed / Input Data]"),
widgets.HBox([self.fs, self.N]),
widgets.Label(value="[Environment / Options]"),
self.show_calc,
widgets.HBox([self.btn_run, self.btn_reset, self.btn_save]),
widgets.HBox([self.btn_show_log, self.btn_clear_log])
])
# Handlers
self.btn_run.on_click(self.on_run)
self.btn_reset.on_click(self.on_reset)
self.btn_save.on_click(self.on_save)
self.btn_show_log.on_click(self.on_show_log)
self.btn_clear_log.on_click(self.on_clear_log)
def on_run(self, b):
params = {'A': self.A.value, 'Tw': self.Tw.value, 'fs': self.fs.value, 'N': self.N.value}
with self.output:
clear_output(wait=True)
if not self.model.validate(params): return
try:
res = self.model.calculate(params)
if self.show_calc.value:
self.model.print_calculation_steps(params, res)
self.current_fig = self.viz.create_plot(res, self.model.ghost_data)
plt.show()
# Log Data
log_entry = {
'timestamp': datetime.datetime.now().isoformat(),
'params': params,
'key_outputs': {'peak': res['peak_mag']}
}
self.model.logs.append(log_entry)
self.model.ghost_data = res
print("シミュレーション完了 / Simulation complete.")
except Exception as e:
print(f"エラーが発生しました / Error occurred: {e}")
def on_reset(self, b):
self.A.value = 1.0
self.Tw.value = 0.5
self.fs.value = 10.0
self.N.value = 128
self.model.ghost_data = None
self.model.logs = []
with self.output:
clear_output()
print("リセットしました / Simulation reset. Ghost and logs cleared.")
def on_save(self, b):
if self.current_fig is None:
with self.output: print("図が生成されていません / No figure generated.")
return
now = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"pulse_fft_result_{now}.pdf"
self.current_fig.savefig(filename, format='pdf')
with self.output:
print(f"PDF保存完了: {filename} / PDF saved successfully.")
print("ダウンロードを開始します... / Starting download...")
files.download(filename)
def on_show_log(self, b):
with self.output:
if not self.model.logs:
print("ログが空です / Log is empty.")
return
print("--- 実験履歴 (Experiment Logs) ---")
for entry in self.model.logs:
print(f"Time: {entry['timestamp']} | Params: {entry['params']} | Peak: {entry['key_outputs']['peak']:.4f}")
def on_clear_log(self, b):
self.model.logs = []
with self.output: print("ログを消去しました / Logs cleared.")
# ==========================================
# 6. Initialization
# ==========================================
if __name__ == "__main__":
print_documentation()
model = TheoreticalModel()
viz = Visualizer()
ui = UIBuilder(model, viz)
display(ui.ui_layout, ui.output)
================================================================================
[README: 孤立パルス(長方形波)の連続・離散フーリエ変換シミュレータ]
-
概要 (Overview):
このプログラムは、時間領域における孤立パルス(長方形波)のフーリエ変換をシミュレーションします。
理論的な連続フーリエ変換(CFT: Sinc関数)と、計算機による高速フーリエ変換(FFT)の結果を
比較・検証することが可能です。サンプリング定理や周波数分解能の理解を助けます。 -
使い方 (Usage):
- [Model Parameters]: パルスの振幅 A と パルス幅 Tw を設定します。
- [Observed / Input Data]: サンプリング周波数 fs と データ点数 N を設定します。
- "Run Simulation" ボタンで計算と描画を実行します。
- "Save PDF" ボタンで結果をPDFとして保存・ダウンロードします。
-
支配方程式 (Governing Equations):
- 孤立パルスの定義 (Rectangular Pulse):
x(t) = A (|t| <= Tw/2), 0 (|t| > Tw/2) - 連続フーリエ変換 (Continuous Fourier Transform):
X(f) = A * Tw * sinc(f * Tw)
ここで sinc(x) = sin(pi * x) / (pi * x) と定義します。 - 離散フーリエ変換 (Discrete Fourier Transform):
X[k] = sum_{n=0}^{N-1} x[n] * exp(-j * 2 * pi * n * k / N)
- 孤立パルスの定義 (Rectangular Pulse):
-
変数定義 (Definitions):
- A: 振幅 (Amplitude)
- Tw: パルス幅 (Pulse Width [s])
- fs: サンプリング周波数 (Sampling Frequency [Hz])
- N: データ点数 (Number of Points)
- df: 周波数分解能 = fs / N (Frequency Resolution [Hz])
================================================================================
--- 理論計算プロセスの表示 (Calculation Steps) ---
-
連続フーリエ変換のピーク値 (CFT Peak Value):
X(0) = A * Tw = 1.0 * 0.5 = 0.5 -
第1零点周波数 (First Zero Frequency):
f_zero = 1 / Tw = 1 / 0.5 = 2.0000 [Hz] -
周波数分解能 (Frequency Resolution):
df = fs / N = 10.0 / 128 = 0.0781 [Hz]
