0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

理論的な連続フーリエ変換(CFT)と離散フーリエ変換(FFT)を比較検証

Posted at
# 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()

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?