# Program Name: Fourier_Transform_Analysis_Simulator_V3
# Creation Date: 2026-01-09
# Purpose: Interactive simulation with PDF export capability
# 1. README
def print_documentation():
print("=========================================================================")
print("【フーリエ変換インタラクティブシミュレータ / Fourier Transform Simulator】")
print("=========================================================================")
print("■ 目的 (Purpose)")
print(" 矩形パルス、ガウス関数、指数減衰信号の3つの波形について、")
print(" 理論的な連続フーリエ変換(CFT)と離散フーリエ変換(FFT)を比較検証します。")
print("")
print("■ 支配方程式 (Governing Equations)")
print(" 1. 矩形パルス (Rectangular Pulse):")
print(" x(t) = 1 (|t| <= W/2) -> X(f) = W * sinc(f * W)")
print(" 2. ガウス関数 (Gaussian Function):")
print(" x(t) = exp(-a * t^2) -> X(f) = sqrt(pi / a) * exp(-(pi * f)^2 / a)")
print(" 3. 指数減衰信号 (Exponential Decay):")
print(" x(t) = exp(-a * t) * u(t) -> X(f) = 1 / (a + j * 2 * pi * f)")
print("")
print("■ 使い方 (Usage)")
print(" 1. [Model Parameters] で波形とパラメータ(aまたはW)を選択。")
print(" 2. [Observed / Input Data] でサンプリング設定を入力。")
print(" 3. [Run Simulation] で計算とプロットを実行。")
print(" 4. [Save PDF] で結果をPDFとしてダウンロード。")
print("=========================================================================")
# 2. Setup Code
# !pip install numpy matplotlib ipywidgets
import numpy as np
import matplotlib.pyplot as plt
import datetime
import ipywidgets as widgets
from google.colab import files
from IPython.display import display, clear_output
# Global setting
np.random.seed(42)
plt.style.use('seaborn-v0_8-muted')
# 3. TheoreticalModel
class FourierModel:
def __init__(self):
self.history = []
def validate(self, params):
if params['fs'] <= 0 or params['n_points'] <= 0:
print("【Error】サンプリング周波数と点数は正の値である必要があります。")
print("[Error] Fs and N must be positive.")
return False
if params['param_a'] <= 0:
print("【Error】パラメータ(a/W)は0より大きい必要があります。")
print("[Error] Parameter a/W must be > 0.")
return False
return True
def calculate(self, params):
fs = params['fs']
n = params['n_points']
wave_type = params['wave_type']
a = params['param_a']
# Time axis (Centered)
t = np.linspace(-n/(2*fs), n/(2*fs), n, endpoint=False)
dt = t[1] - t[0]
# 1. Time Domain Signal
if wave_type == "Rectangular":
x = np.where(np.abs(t) <= a/2, 1.0, 0.0)
elif wave_type == "Gaussian":
x = np.exp(-a * (t**2))
else: # Exponential Decay
x = np.where(t >= 0, np.exp(-a * t), 0.0)
# 2. FFT Calculation
# Shift to align t=0 with index 0 for FFT
x_fft_input = np.fft.ifftshift(x)
X_fft_raw = np.fft.fft(x_fft_input) * dt
X_fft = np.fft.fftshift(X_fft_raw)
freqs = np.fft.fftshift(np.fft.fftfreq(n, d=dt))
# 3. Theoretical CFT
f_theo = np.linspace(freqs.min(), freqs.max(), 1000)
if wave_type == "Rectangular":
X_theo = a * np.sinc(f_theo * a)
elif wave_type == "Gaussian":
X_theo = np.sqrt(np.pi / a) * np.exp(-(np.pi * f_theo)**2 / a)
else: # Exp Decay
X_theo_complex = 1 / (a + 1j * 2 * np.pi * f_theo)
X_theo = np.abs(X_theo_complex)
return t, x, freqs, np.abs(X_fft), f_theo, np.abs(X_theo)
def print_calculation_steps(self, params):
print("\n--- Theoretical Formula / 理論式 ---")
w_type = params['wave_type']
val = params['param_a']
if w_type == "Rectangular":
print(f"X(f) = {val} * sinc(f * {val})")
elif w_type == "Gaussian":
print(f"X(f) = sqrt(pi / {val}) * exp(-(pi * f)^2 / {val})")
else:
print(f"|X(f)| = 1 / sqrt({val}^2 + (2 * pi * f)^2)")
print("FFT normalized by dt to match CFT amplitude.")
# 4. Visualizer
class Visualizer:
def __init__(self):
self.fig = None
self.ghost = None
def plot(self, t, x, f_fft, X_fft, f_theo, X_theo, wave_type, use_ghost=False):
self.fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))
# Plot 1: Time Domain
ax1.plot(t, x, label='Current', color='dodgerblue', linewidth=2)
if use_ghost and self.ghost:
ax1.plot(self.ghost['t'], self.ghost['x'], '--', color='gray', alpha=0.4, label='Previous')
ax1.set_title(f'Time Domain: {wave_type}')
ax1.set_xlabel('Time [s]')
ax1.set_ylabel('Amplitude')
ax1.grid(True)
ax1.legend()
# Plot 2: Frequency Domain
ax2.plot(f_fft, X_fft, 'o', markersize=3, label='FFT Result', color='crimson', alpha=0.6)
ax2.plot(f_theo, X_theo, label='Theory (CFT)', color='black', linewidth=1)
ax2.set_title('Frequency Domain: Magnitude')
ax2.set_xlabel('Frequency [Hz]')
ax2.set_ylabel('|X(f)|')
ax2.set_xlim(-10, 10)
ax2.grid(True)
ax2.legend()
plt.tight_layout()
plt.show()
self.ghost = {'t': t, 'x': x}
# 5. UIBuilder
class App:
def __init__(self):
self.model = FourierModel()
self.viz = Visualizer()
self.out = widgets.Output()
# [Model Parameters]
self.wave_type = widgets.Dropdown(options=["Rectangular", "Gaussian", "Exp Decay"], value="Rectangular", description="Signal Type")
self.param_a = widgets.BoundedFloatText(value=1.0, min=0.01, max=10.0, step=0.1, description="a / Width")
# [Observed / Input Data]
self.fs = widgets.FloatText(value=50.0, description="Fs [Hz]")
self.n_pts = widgets.IntText(value=1024, description="Points [N]")
# [Environment / Options]
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_log = widgets.Button(description="Show Log / 履歴表示")
self.btn_clear = widgets.Button(description="Clear Log / 履歴消去")
self.btn_run.on_click(self.run)
self.btn_reset.on_click(self.reset)
self.btn_save.on_click(self.save)
self.btn_log.on_click(self.log)
self.btn_clear.on_click(self.clear)
def start(self):
display(widgets.VBox([
widgets.HTML("<b>[Model Parameters]</b>"), self.wave_type, self.param_a,
widgets.HTML("<b>[Observed / Input Data]</b>"), self.fs, self.n_pts,
widgets.HTML("<b>[Environment / Options]</b>"),
widgets.HBox([self.btn_run, self.btn_reset, self.btn_save]),
widgets.HBox([self.btn_log, self.btn_clear]),
self.out
]))
def run(self, _):
with self.out:
clear_output(wait=True)
p = {'wave_type': self.wave_type.value, 'param_a': self.param_a.value, 'fs': self.fs.value, 'n_points': self.n_pts.value}
if not self.model.validate(p): return
try:
res = self.model.calculate(p)
self.model.print_calculation_steps(p)
self.viz.plot(*res, p['wave_type'], use_ghost=True)
self.model.history.append({"time": datetime.datetime.now().strftime("%H:%M:%S"), "params": p})
print("【Success】計算完了 / Calculation completed.")
except Exception as e:
print(f"【Error】{e}")
def reset(self, _):
self.wave_type.value = "Rectangular"; self.param_a.value = 1.0
self.fs.value = 50.0; self.n_pts.value = 1024
self.viz.ghost = None; self.model.history = []
with self.out:
clear_output(); print_documentation()
print("【Reset】リセット完了 / Reset done.")
def save(self, _):
if self.viz.fig is None:
with self.out: print("【Warning】プロットがありません / No plot.")
return
fn = f"fourier_analysis_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf"
self.viz.fig.savefig(fn, format='pdf')
with self.out:
print(f"【Info】PDFをダウンロードします: {fn}")
files.download(fn)
def log(self, _):
with self.out:
print("\n--- History / 履歴 ---")
for entry in self.model.history: print(entry)
def clear(self, _):
self.model.history = []
with self.out: print("【Log】履歴消去完了 / Log cleared.")
# 6. Initialization
if __name__ == "__main__":
print_documentation()
App().start()
Register as a new user and use Qiita more conveniently
- You get articles that match your needs
- You can efficiently read back useful information
- You can use dark theme