# Program Name: Fourier_Series_vs_FFT_Comparison_Simulator_Final_Bilingual
# Creation Date: 2026-01-09
# Purpose: Interactive simulation with PDF export and Japanese Font Support
# ---------------------------------------------------------
# 1. README
# ---------------------------------------------------------
def print_documentation():
print("""
=========================================================
【シミュレータ名】フーリエ級数展開とFFTの比較シミュレータ (日本語表示対応版)
(Fourier Series vs FFT Comparison Simulator - JP Font Support)
【目的 / Purpose】
周期信号のフーリエ級数合成とFFT解析の比較を行います。
Google Colab上での日本語文字化け(豆腐)を回避するための設定を含んでいます。
【修正内容 / Fix Notes】
日本語が表示されない問題(豆腐現象)を解決するため、
グラフ描画時に特定のフォントを指定せず、標準の非英語フォントとの互換性を
高める設定、または英語ラベルへのフォールバック構造を適用しました。
【支配方程式 / Governing Equations】
- Square Wave (矩形波):
f(t) = (4/pi) * sum( sin(2*pi*(2n-1)*f0*t) / (2n-1) )
=========================================================
""")
# ---------------------------------------------------------
# 2. Setup Code
# ---------------------------------------------------------
# !pip install matplotlib numpy ipywidgets
import numpy as np
import matplotlib.pyplot as plt
import ipywidgets as widgets
from datetime import datetime
from google.colab import files
import os
# 日本語文字化け対策: フォントのキャッシュをクリアし、
# 非ASCII文字が含まれてもエラーにならないよう設定
plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['axes.unicode_minus'] = False # マイナス記号の文字化け防止
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['f0'] <= 0 or params['fs'] <= 0:
print("[Error] 周波数は0より大きい必要があります。 / Frequencies must be > 0.")
return False
return True
def calculate(self, p):
t = np.linspace(0, p['duration'], int(p['fs'] * p['duration']), endpoint=False)
y_series = np.zeros_like(t)
n_range = range(1, p['n_terms'] + 1)
# Fourier Series Synthesis
if "Square" in p['wave_type']:
for n in n_range:
k = 2 * n - 1
y_series += (4 / (np.pi * k)) * np.sin(2 * np.pi * k * p['f0'] * t)
elif "Sawtooth" in p['wave_type']:
for n in n_range:
y_series += (2 / (np.pi * n)) * ((-1)**(n+1)) * np.sin(2 * np.pi * n * p['f0'] * t)
elif "Triangle" in p['wave_type']:
for n in range(p['n_terms']):
k = 2 * n + 1
y_series += (8 / (np.pi**2)) * ((-1)**n / k**2) * np.sin(2 * np.pi * k * p['f0'] * t)
elif "Full-Rect" in p['wave_type']:
y_series = np.full_like(t, 2/np.pi)
for n in n_range:
y_series -= (4 / (np.pi * (4 * n**2 - 1))) * np.cos(2 * np.pi * 2 * n * p['f0'] * t)
elif "Half-Rect" in p['wave_type']:
y_series = np.full_like(t, 1/np.pi) + 0.5 * np.sin(2 * np.pi * p['f0'] * t)
for n in n_range:
y_series -= (2 / (np.pi * (4 * n**2 - 1))) * np.cos(2 * np.pi * 2 * n * p['f0'] * t)
y_series += np.random.normal(0, p['noise'], len(t))
N = len(t)
yf = np.fft.fft(y_series)
xf = np.fft.fftfreq(N, 1/p['fs'])[:N//2]
amp = 2.0/N * np.abs(yf[0:N//2])
return t, y_series, xf, amp
def print_calculation_steps(self, p, results):
print(f"\n[Calculation Steps / 計算過程]")
print(f"Wave / 波形: {p['wave_type']}")
print(f"Sampling / サンプリング: {p['fs']} Hz")
print(f"Terms / 項数: {p['n_terms']}")
# ---------------------------------------------------------
# 4. Visualizer
# ---------------------------------------------------------
class Visualizer:
def __init__(self):
self.fig = None
self.ghost = None
def plot(self, t, y, xf, amp, p, clear_ghost=False):
if clear_ghost: self.ghost = None
self.fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))
# Time Domain Plot
if self.ghost:
ax1.plot(self.ghost['t'], self.ghost['y'], color='gray', alpha=0.2, label='Previous')
ax1.plot(t, y, lw=1.5, label='Current')
# 日本語が含まれると化けるため、グラフ内タイトルは英語メイン+日本語併記を避けるか、
# 以下のようにフォント設定が不安定な環境でも安全な表記にします
ax1.set_title(f"Time Domain: {p['wave_type']}")
ax1.set_xlabel("Time [s]")
ax1.set_ylabel("Amplitude")
ax1.grid(True)
ax1.legend()
# Frequency Domain Plot
if self.ghost:
ax2.plot(self.ghost['xf'], self.ghost['amp'], color='gray', alpha=0.2, label='Previous')
ax2.stem(xf, amp, linefmt='C1-', markerfmt='C1.', basefmt=' ', label='FFT Spectrum')
ax2.set_title("Frequency Domain: Amplitude Spectrum")
ax2.set_xlabel("Frequency [Hz]")
ax2.set_ylabel("Magnitude")
ax2.set_xlim(0, max(p['f0'] * 10, 10))
ax2.grid(True)
ax2.legend()
plt.tight_layout()
plt.show()
self.ghost = {'t': t, 'y': y, 'xf': xf, 'amp': amp}
# ---------------------------------------------------------
# 5. UIBuilder
# ---------------------------------------------------------
model = FourierModel()
viz = Visualizer()
# UIラベルはウィジェット上で日本語を使用(ここは化けません)
w_type = widgets.Dropdown(
options=[
('Square (矩形波)', 'Square'),
('Sawtooth (鋸歯状波)', 'Sawtooth'),
('Triangle (三角波)', 'Triangle'),
('Full-Rect (全波整流)', 'Full-Rect'),
('Half-Rect (半波整流)', 'Half-Rect')
],
value='Square', description='Wave Type / 波形:', style={'description_width': 'initial'}
)
w_f0 = widgets.FloatText(value=1.0, description='f0 [Hz]:', style={'description_width': 'initial'})
w_n = widgets.IntText(value=10, description='Terms / 項数:', style={'description_width': 'initial'})
w_fs = widgets.FloatText(value=200.0, description='fs [Hz]:', style={'description_width': 'initial'})
w_dur = widgets.FloatText(value=2.0, description='Duration [s]:', style={'description_width': 'initial'})
w_noise = widgets.BoundedFloatText(value=0.0, min=0, max=1.0, step=0.01, description='Noise / ノイズ:', style={'description_width': 'initial'})
btn_run = widgets.Button(description="Run Simulation / 実行", button_style='primary')
btn_reset = widgets.Button(description="Reset / リセット")
btn_pdf = widgets.Button(description="Save PDF / PDF保存", button_style='success')
btn_log = widgets.Button(description="Show Log / 履歴表示")
btn_clr = widgets.Button(description="Clear Log / 履歴消去")
output = widgets.Output()
def run_sim(b):
with output:
output.clear_output()
params = {'wave_type': w_type.value, 'f0': w_f0.value, 'n_terms': w_n.value,
'fs': w_fs.value, 'duration': w_dur.value, 'noise': w_noise.value}
if model.validate(params):
t, y, xf, amp = model.calculate(params)
model.print_calculation_steps(params, (t, y, xf, amp))
# グラフ内での文字化けを防ぐため、w_typeの「表示名」ではなく「内部値(英語)」を渡す
viz.plot(t, y, xf, amp, params)
model.history.append({'time': datetime.now().isoformat(), 'p': params})
print(f"Success / 実行成功: {datetime.now().strftime('%H:%M:%S')}")
def reset_sim(b):
w_type.value = 'Square'
w_f0.value, w_n.value, w_fs.value = 1.0, 10, 200.0
with output:
output.clear_output()
print("Reset Completed / リセット完了")
def save_pdf(b):
if viz.fig:
fname = f"fourier_sim_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf"
viz.fig.savefig(fname, format='pdf')
with output:
print(f"Downloading PDF: {fname}")
files.download(fname)
btn_run.on_click(run_sim); btn_reset.on_click(reset_sim); btn_pdf.on_click(save_pdf)
btn_log.on_click(lambda x: print(model.history)); btn_clr.on_click(lambda x: model.history.clear())
ui = widgets.VBox([
widgets.HTML("<b>[Model Parameters]</b>"), widgets.HBox([w_type, w_f0, w_n]),
widgets.HTML("<b>[Observed Data]</b>"), widgets.HBox([w_fs, w_dur]),
widgets.HTML("<b>[Environment]</b>"), widgets.HBox([w_noise]),
widgets.HBox([btn_run, btn_reset, btn_pdf, btn_log, btn_clr])
])
# ---------------------------------------------------------
# 6. Initialization
# ---------------------------------------------------------
print_documentation()
display(ui, output)
Would you like me to try installing a specific Japanese font like 'IPAexGothic' in the background to make the graph titles also show Japanese characters?