「マウスでぐるっと描いた適当な曲線が、回る円の組み合わせだけで完璧に再現できる」 — 初めて聞いたとき「いやいや、そんなわけないでしょ」と思いませんでしたか?
でも、これが事実なんです。しかも、その原理はあなたのスマホで毎日使われている音声圧縮(MP3)や画像圧縮(JPEG)の根幹でもあります。
今回は、実際にブラウザで動く フーリエ円(エピサイクル)シミュレーター を題材に、「なぜ円だけでどんな形も描けるのか」 を、数式とコードの両方からガッツリ解説します。
▶ 今回のシミュレーター: フーリエ円(エピサイクル)シミュレーター (ブラウザで動作・登録不要)
導入 — 「円を足す」という発想の転換
僕たちが「円」と聞いて想像するのは、一定の半径でぐるぐる回る単純な図形です。ところが、半径・回転速度・スタート時の向き(位相)がバラバラな円を何十個も重ねて、その先端の軌跡を追いかけると…なんと星の形や四角形、あなたのサインまでもが描けてしまう。
これ、直感的にはかなり奇妙ですよね。
- 円は永遠に同じ軌道をなぞるだけ
- なのに、その「足し算」が複雑な閉曲線を生む
この「不思議」を解く鍵が、離散フーリエ変換(DFT) です。一言で言えば、「どんな複雑な波形も、異なる周波数の正弦波(サイン波)の重ね合わせで表現できる」 という原理。これを「円運動」の形に翻訳したのが、エピサイクル(フーリエ円)なんです。
ざっくり本質 — たとえ話で直感をつかむ
イメージしてみてください。
あなたは宇宙の中心に立っています。周りを、大きさも速さも違う惑星がぐるぐる回っています。その「惑星の先端」にペンを取り付けて、紙に軌跡を描かせるとします。
- 1つ目の惑星(半径5、ゆっくり回転)→ 大きな円を描く
- 2つ目の惑星(半径2、3倍の速さで回転)→ 小さな円をぐるぐる
- 3つ目の惑星(半径1、5倍の速さ)→ さらに細かい円
…これらを同時に回すと、ペン先は複雑な軌道を描き始めます。この「惑星の数」を増やし、各惑星の半径・回転速度・初期角度を適切に選べば、理論上どんな閉曲線も再現できる — それがフーリエ円の本質です。
「どんな閉曲線も、異なる半径・速度・位相を持つ回転円の和で表現できる」
この「適切な選び方」を計算するのが、離散フーリエ変換(DFT) の役割です。
数式で理解する — DFTが「円の設計図」を出力する仕組み
まず、描きたい曲線を複素平面上の点の集まりとして捉えます。時刻 $t$ におけるペン先の位置を複素数 $z(t) = x(t) + i,y(t)$ と書くと、フーリエ級数展開はこうなります。
z(t) = \sum_{k} c_k\, e^{\,i\,2\pi k t/T}
各項の意味はこうです。
- $k$:何番目の円か(整数)
- $c_k$:複素数 — この1つの値に「半径」と「初期位相」の情報が詰まっている
- $e^{,i,2\pi k t/T}$:回転 — 時刻 $t$ に応じてぐるぐる回る単位円上の点
- $T$:曲線を一周するのにかかる時間
つまり、$c_k$ が「k番目の円の設計図」 なんです。この $c_k$ をどうやって求めるか? それがDFTです。
c_k = \frac{1}{N}\sum_{n=0}^{N-1} z[n]\, e^{-i\,2\pi k n/N}
この式の意味を日本語に訳すと:
- 描いた曲線を $N$ 個の点 $z[0], z[1], ..., z[N-1]$ としてサンプリングする
- 各点 $z[n]$ に、周波数 $k$ の回転(逆向き) を掛ける
- 全部足して、$N$ で割る → これが $c_k$
この $c_k$ が複素数であることが超重要です。複素数は「大きさ(絶対値)」と「角度(偏角)」の2つの情報を持ちます。
- $|c_k|$(大きさ)→ k番目の円の半径
- $\arg(c_k)$(角度)→ k番目の円の初期位相(スタート時の向き)
実務でハマるポイント①: この $c_k$ を逆に使って $z(t)$ を再現するとき、位相情報を間違えると波形が完全に崩れる。半径だけ正しくてもダメなんです。シミュレーターで各アームがバラバラの向きからスタートするのは、この位相情報を可視化しているからです。
コードで実装する — 20行で理解するDFTとエピサイクル ★最重要★
ここがこの記事の核です。シミュレーターの計算ロジックを、実際に動くJavaScriptコードで再現します。
/**
* フーリエ円(エピサイクル)の核心計算
* マウスで描いた曲線 → DFT → 円のパラメータ → 再描画
*/
// 1. 描かれた曲線を複素数の配列として取得(例:100点)
const points = []; // 実際にはマウス座標から生成
// 例: points = [{x: 10, y: 20}, {x: 12, y: 22}, ...]
// 2. 複素数に変換 (z[n] = x[n] + i*y[n])
const N = points.length;
const z = points.map(p => ({ re: p.x, im: p.y }));
// 3. DFT: 各周波数kの係数c_kを計算
function computeDFT(z, N) {
const coeffs = [];
for (let k = 0; k < N; k++) {
let sumRe = 0, sumIm = 0;
for (let n = 0; n < N; n++) {
// e^{-i*2π*k*n/N} = cos(θ) - i*sin(θ)
const angle = (-2 * Math.PI * k * n) / N;
const cosA = Math.cos(angle);
const sinA = Math.sin(angle);
// 複素数のかけ算: (a+ib)*(c+id) = (ac-bd) + i(ad+bc)
sumRe += z[n].re * cosA - z[n].im * sinA;
sumIm += z[n].re * sinA + z[n].im * cosA;
}
// 平均を取る (1/N)
coeffs.push({
re: sumRe / N,
im: sumIm / N,
// 半径と位相も事前計算しておく
radius: Math.sqrt((sumRe/N)**2 + (sumIm/N)**2),
phase: Math.atan2(sumIm/N, sumRe/N)
});
}
return coeffs;
}
// 4. エピサイクルで時刻tの位置を計算(円の足し算)
function computeEpicycle(t, coeffs, T) {
let x = 0, y = 0;
for (let k = 0; k < coeffs.length; k++) {
const c = coeffs[k];
// c_k * e^{i*2π*k*t/T}
const angle = (2 * Math.PI * k * t) / T;
// 実部: c.re*cos(θ) - c.im*sin(θ)
// 虚部: c.re*sin(θ) + c.im*cos(θ)
x += c.re * Math.cos(angle) - c.im * Math.sin(angle);
y += c.re * Math.sin(angle) + c.im * Math.cos(angle);
}
return { x, y };
}
// 5. 使用例
const coeffs = computeDFT(z, N);
const T = N; // 一周をNステップで描画
for (let t = 0; t < T; t++) {
const pos = computeEpicycle(t, coeffs, T);
// pos.x, pos.y をキャンバスにプロット
}
このコードで何が起きているか:
-
computeDFT:描いた曲線の点群から、各周波数 $k$ の係数 $c_k$(複素数)を計算 -
computeEpicycle:その係数を使って、時刻 $t$ の位置を「円の足し算」で再現 - DFTと逆DFT(再構成)をペアで実装しているのがポイント
実務でハマるポイント②: DFTの計算量は $O(N^2)$。点の数 $N$ が1000を超えるとブラウザが固まります。実務では高速フーリエ変換(FFT) を使い $O(N \log N)$ に落とします。このシミュレーターも、点の数を適度に制限しているのはそのためです。
数値例で確かめる — たった4つの円で「四角形」を近似
実際に数値を入れて計算してみましょう。単純な例として、正方形の頂点4点をDFTで分解します。
元の点(正方形の頂点、中心を原点とする):
| n | z[n] (複素数) | x | y |
|---|---|---|---|
| 0 | 1 + i | 1 | 1 |
| 1 | -1 + i | -1 | 1 |
| 2 | -1 - i | -1 | -1 |
| 3 | 1 - i | 1 | -1 |
$N=4$ としてDFTを計算します。$k=0$(直流成分)から見てみましょう。
$k=0$ の場合:
c_0 = \frac{1}{4} \sum_{n=0}^{3} z[n] \cdot e^{0} = \frac{1}{4} ( (1+i) + (-1+i) + (-1-i) + (1-i) ) = \frac{1}{4} (0) = 0
$k=1$ の場合:
c_1 = \frac{1}{4} \sum_{n=0}^{3} z[n] \cdot e^{-i\,2\pi n/4}
各項を計算:
- $n=0$: $(1+i) \cdot e^{0} = 1+i$
- $n=1$: $(-1+i) \cdot e^{-i\pi/2} = (-1+i) \cdot (-i) = (-1)(-i) + i(-i) = i + 1 = 1 + i$
- $n=2$: $(-1-i) \cdot e^{-i\pi} = (-1-i) \cdot (-1) = 1 + i$
- $n=3$: $(1-i) \cdot e^{-i3\pi/2} = (1-i) \cdot (i) = i + 1 = 1 + i$
よって $c_1 = \frac{1}{4} (4 + 4i) = 1 + i$。半径は $\sqrt{1^2 + 1^2} = \sqrt{2}$、位相は $45^\circ$。
$k=2$(同様に計算): $c_2 = 0$
$k=3$($k=1$の共役): $c_3 = 1 - i$
結果: たった2つの非ゼロ係数 $c_1, c_3$ だけで、正方形が表現できることがわかります。
z(t) = (1+i)e^{i2\pi t/4} + (1-i)e^{i6\pi t/4}
この式を時刻 $t=0,1,2,3$ で計算すると、元の4点が完全に再現されます。円は2つだけ。これが「少ない円でも大まかな形が再現できる」理由です。
実務でハマるポイント③: 上の例は「点の数=円の数」で完全再現していますが、マウスで描いた曲線はもっと多くの点を含むため、$N$ を最大にしても「離散点を通る再現」に過ぎません。滑らかな曲線を完全に再現するには無限の円が必要 — だから「近似」なんです。
シミュレーターで遊ぶ — 3つの実験で体感するフーリエ変換
実験1:「円の数」を変えて近似精度の変化を見る
- シミュレーターで星の形を描いてみてください
- 「円の数 N」スライダーを 最小(例:3) に → ぼんやりした星の形
- 徐々に Nを増やす → だんだんシャープに
- N = 元の点の数 に → 完全再現
なぜ? 少ない円(低周波成分)は大まかな形状を担当。細かいギザギザ(高周波成分)は、円の数が増えると再現されるようになります。
実験2:「速度」を変えて各円の回転差を見る
- 適当な曲線を描く
- 「速度」スライダーを 最大 に
- 各アーム(腕)の回転速度が明らかに違うのが見えるはず
- 最小 にすると、ゆっくりになり違いがわかりにくい
なぜ? k番目の円の角速度は $\omega_k = \frac{2\pi k}{T}$ で固定。k=1の円が1回転する間に、k=2の円は2回転、k=3は3回転します。速度スライダーはこの基本速度全体を一括で変えているだけです。
実験3:「閉じていない曲線」で歪みを観察
- わざと始点と終点が一致しない曲線を描く(例:直線)
- 再現時に不連続な跳びや振動(ギブス現象) が現れる
なぜ? DFTは「閉じた曲線」を前提としています。始点と終点がずれると、そのギャップを埋めるために高周波成分に大きな値が現れ、結果的に歪みます。実務でも、FFTをかける前に窓関数を使って端を滑らかにするのはこのためです。
現場でハマるポイント — 実務でフーリエ変換を使うときの注意点
1. 「円の数Nを最大にすれば完璧」は嘘ではないが…誤解
理論上は元の点の数と同じ円で完全再現できます。しかし、マウスで描いた曲線は最初から離散的な点の集まり。Nを最大にしても「離散点を通る再現」に過ぎず、点と点の間の曲線は線形補間などで補われています。
実務教訓: 「完全再現」より「必要な精度を達成する最小のN」を見極めることが重要。JPEGでは人間の目が感知しにくい高周波成分を大胆にカットすることで、ファイルサイズを1/10以下に圧縮しています。
2. 位相情報を軽視すると大惨事
「半径だけ正しければ形は再現できるのでは?」— できません。先ほどの正方形の例で、$c_1$ の位相を $45^\circ$ から $0^\circ$ に変えてみてください。正方形ではなく菱形になります。
実務教訓: 音声処理で位相を無視して逆変換すると、全く別の音になります。イコライザーで位相までいじる「フェイザー」エフェクトは、この原理を逆手に取ったものです。
3. サンプリング点数と周波数分解能のトレードオフ
円の数(=周波数成分の数)を増やすと精度は上がりますが、計算量は $O(N^2)$ で増加。実務ではFFTを使うとはいえ、サンプリング点数を増やしすぎると処理が重くなる。
実務教訓: 必要な周波数分解能から逆算してサンプリング点数を決める。例えば音声なら44.1kHzサンプリングで20Hz〜20kHzをカバーする、といった設計をします。
まとめ — 3つの要点 + 実践への誘い
- フーリエ円の本質は「複素指数関数の重ね合わせ」 — どんな閉曲線も、異なる半径・速度・位相の円の和で表現できる
- DFTが「設計図」を出力 — 描いた曲線を $N$ 点サンプリングし、$c_k$(複素数)を計算。その絶対値が半径、偏角が初期位相
- 実務では「どの円を残すか」が勝負 — 小さい円(高周波成分)を省くことでデータ圧縮。JPEG、MP3、振動診断に応用
この記事で解説した原理は、あなたの手元のブラウザで今すぐ体験できます。
▶ フーリエ円(エピサイクル)シミュレーター — ブラウザで即動作、登録不要
マウスで曲線を描いて、スライダーを動かすだけで「円の足し算」が形を生む様子をリアルタイムで確認できます。「円の数」を減らしたときの近似の様子は、まさにJPEG圧縮の原理を目で見ているのと同じです。
NovaSolverでは700以上の工学シミュレーターを無料公開中 👉 一覧はこちら
「円だけですべての形が描ける」 — この事実を知ったときの驚きは、フーリエ変換の本質を体感した瞬間でもあります。理論と実装、そして実際に動くツール — この3つが揃って初めて、本当の理解に到達できるのだと、僕は信じています。