見出し画像

Processing でグラフを描く⑦ フーリエ級数

Processing でグラフを描く 第7回目です。
前回「バラ曲線とジェネラティブアート」で、「フーリエ級数」という言葉が出てきました。いきなりフーリエ級数と言われて、「なんじゃそれ?」と思った方もおられたでしょうね。
私の数学のバイブルである「オイラーの贈物―人類の至宝eiπ=-1を学ぶ」の第10章に、このフーリエ級数が出てくるのです。昔、本を読みながら、この式をグラフ化したいと思ったことを思い出しました。
今回は「フーリエ級数」のグラフ化に挑戦してみます。

フーリエ級数とは

$$
\frac{a_0}{2} + \displaystyle\sum_{n=1}^\infty (a_n cos({nx} )+ b_n sin({nx}))
$$

上記の「三角関数の和の形」で表される級数をフーリエ級数といいます。級数とは数列の無限和のことです。周期関数を三角関数を用いて展開するときに使われます。
といわれても、なんのことやら意味不明です。どうして周期関数を三角関数に分解しなければならないのかって思ってしまいます。そういうときこぞ、グラフの出番です。直感的にフーリエ級数を捉えることができます。

フーリエ級数① 凸凹のある三角関数

$$
y = sin{x} + \displaystyle\sum_{n=1}^\infin \frac{sin{(2 n + 1) x}}{4 n}
$$

BACKGROUND_COLOR = color(0, 44, 77)
colors = [
    [255, 255, 255],  # white
    [255, 127, 0],  # orange
    [255, 0, 255],  # magenta
    [127, 127, 255],  # lightblue
    [255, 255, 0],  # yellow
    [0, 255, 0],  # lime
    [255, 127, 127],  # pink
    [127, 127, 127],  # gray
    [191, 191, 191],  # lightgray
    [0, 255, 255],  # cyan
    [127, 0, 127],  # purple
    [0, 0, 255],  # blue
    [127, 0, 0],  # brown
    [0, 127, 0],  # green
    [255, 0, 0],  # red
    [0, 0, 0],  # black
]
SIZE = 1000
STEP = 50
RADIUS = 300
points_list = []


def setup():
    size(SIZE, SIZE)

    # グラフのデータ
    for j in range(0, 5):
        points_list.append(
            [fourier_sine_series(i, j) for i in range(360)]
        )


def draw():
    background(BACKGROUND_COLOR)
    strokeWeight(1)
    stroke(0, 255, 0, 50)
    for i in range(int(SIZE / STEP)):
        line(-2000, i * STEP, 2000, i * STEP)
        line(i * STEP, -2000, i * STEP, 2000)
    pushMatrix()
    translate(50, height * 3 / 5)
    noFill()
    stroke(0, 255, 0)
    line(-2000, 0, 2000, 0)
    line(0, -2000, 0, 2000)
    # グラフを描く
    count = min(frameCount - 1, len(points_list[0]) * 2)
    for j, points in enumerate(points_list):
        stroke(colors[j][0], colors[j][1], colors[j][2])
        for i in range(count):
            x1 = radians(i) * STEP * 2
            y1 = points[i % len(points)] * RADIUS / 4.0
            x2 = radians(i + 1) * STEP * 2
            y2 = points[(i + 1) % len(points)] * RADIUS / 4.0
            line(x1, -y1, x2, -y2)
    popMatrix()


def fourier_sine_series(i, j):
    i = radians(i)
    f = sin(i)
    for k in range(j):
        f += sin(i * (j * 2 + 1)) / float(j * 4)
    return f * 4
fourier series 1

前回の「バラ曲線とジェネラティブアート」で花びらの形を変形するために使った「フーリエ正弦級数(sine のみで表される)」です。正弦関数(sine)の各項にかける係数を適切に決めることで、凸凹のある正弦関数を得ることができます。
$${0 < x < \pi}$$ の範囲で見ると、項の数と山の数が同じになっています。凸凹のある正弦関数を使えば、バラ曲線のバリエーションを増やすことができますね。

フーリエ級数② 2次関数の展開

$$
x^2 =  \frac{1}{3} + \frac{4}{\pi^2}\displaystyle\sum_{n=1}^\infin \frac{cos{n \pi x}}{n^2}\\
(-1 < x < 1)
$$

BACKGROUND_COLOR = color(0, 44, 77)
colors = [
    [255, 255, 255],  # white
    [255, 127, 0],  # orange
    [255, 0, 255],  # magenta
    [127, 127, 255],  # lightblue
    [255, 255, 0],  # yellow
    [0, 255, 0],  # lime
    [255, 127, 127],  # pink
    [127, 127, 127],  # gray
    [191, 191, 191],  # lightgray
    [0, 255, 255],  # cyan
    [127, 0, 127],  # purple
    [0, 0, 255],  # blue
    [127, 0, 0],  # brown
    [0, 127, 0],  # green
    [255, 0, 0],  # red
    [0, 0, 0],  # black
]
SIZE = 1000
STEP = 250


def setup():
    size(SIZE, SIZE)

    # グラフのデータ
    for j in range(0, 5):
        points_list.append(
            [fourier_sine_series(i, j) for i in range(200)]
        )


def draw():
    background(BACKGROUND_COLOR)
    strokeWeight(1)
    stroke(0, 255, 0, 50)
    for i in range(int(SIZE / STEP)):
        line(-2000, i * STEP, 2000, i * STEP)
        line(i * STEP, -2000, i * STEP, 2000)
    pushMatrix()
    # translate(50, height * 3 / 5)
    translate(width / 2, height * 3 / 4)
    noFill()
    stroke(0, 255, 0)
    line(-2000, 0, 2000, 0)
    line(0, -2000, 0, 2000)
    # グラフを描く
    # 元の関数
    stroke(colors[0][0], colors[0][1], colors[0][2], 100)
    for i in range(200):
        x1 = i * STEP / 100.0
        y1 = (i / 100.0) ** 2 * STEP
        x2 = (i + 1) * STEP / 100.0
        y2 = ((i + 1) / 100.0) ** 2 * STEP
        line(x1, -y1, x2, -y2)
        line(-x1, -y1, -x2, -y2)
    # フーリエ級数
    count1 = min(frameCount - 1, 400)
    for j, points in enumerate(points_list):
        # print(points)
        stroke(colors[j][0], colors[j][1], colors[j][2])
        for i in range(count1):
            x1 = i * STEP / 100.0
            y1 = points[i % len(points)] * STEP
            x2 = (i + 1) * STEP / 100.0
            y2 = points[(i + 1) % len(points)] * STEP
            line(x1, -y1, x2, -y2)
            line(-x1, -y1, -x2, -y2)
            print(x1, -y1, x2, -y2)
    popMatrix()


def fourier_sine_series(i, j):
    i = i / 100.0
    f = 1.0 / 3
    for k in range(j):
        f += cos(i * (k + 1) * PI) * 4 * (-1) ** (k + 1) / (PI ** 2 * (k + 1) ** 2)
    return f
fourier series 2

計算過程は省略しますが、2次関数 $${y = x^2}$$ をフーリエ余弦級数(cosine のみで表される)に展開しました。最初の5項までをグラフ化しましたが、元のグラフ(薄い白)に近似していくことが見て取れます。
$${x = -1, x = 1}$$ のあたりはどこまで足しても誤差が出てしまうので、どれくらいの精度を求めるかによって、加える項の数を決めることになります。

このグラフの面白いところは $${x > 1}$$ の部分で、フーリエ級数展開は元のグラフと完全に離れていくところです。これは無限和を計算しても同じです。なぜならフーリエ級数は、2次関数 $${y = x^2}$$ そのものの展開ではなく、「$${(-1 < x < 1)}$$ の範囲を並べて周期関数にした」ときの展開を行ったからです。グラフは正確にそのことを示しています。

もちろん2次関数以上の高次関数や指数関数などもフーリエ級数展開することが可能です。次はフーリエ級数の応用を考えてみましょう。

フーリエ級数の応用(シンセサイザー)

synthesizer

フーリエ級数が使われている有名なケースとして、シンセサイザーがあります。シンセサイザーを大雑把に説明すると、振動数が整数倍の音叉(turning fork)がたくさん入っており、各音叉を基準音叉の何倍($${\frac{1}{3}}$$ など)の振幅で鳴らして、楽器の音を再現する電子楽器のことです。
次に説明する矩形波(くけいは)は、クラリネットなどの木管楽器の音に近いといわれています。三角波は口笛やフルートを、ノコギリ波はチェロやバイオリンの音を近似します。

フーリエ級数③ 矩形波

BACKGROUND_COLOR = color(0, 44, 77)
colors = [
    [255, 255, 255],  # white
    [255, 127, 0],  # orange
    [255, 0, 255],  # magenta
    [127, 127, 255],  # lightblue
    [255, 255, 0],  # yellow
    [0, 255, 0],  # lime
    [255, 127, 127],  # pink
    [127, 127, 127],  # gray
    [191, 191, 191],  # lightgray
    [0, 255, 255],  # cyan
    [127, 0, 127],  # purple
    [0, 0, 255],  # blue
    [127, 0, 0],  # brown
    [0, 127, 0],  # green
    [255, 0, 0],  # red
    [0, 0, 0],  # black
]
SIZE = 1000
STEP = 50
RADIUS = 300
points_list = []


def setup():
    size(SIZE, SIZE)

    # グラフのデータ
    for j in range(0, 5):
        points_list.append(
            [fourier_sine_series(i, j) for i in range(360)]
        )
    points_list.append(
        [fourier_sine_series(i, 100) for i in range(360)]
    )


def draw():
    background(BACKGROUND_COLOR)
    strokeWeight(1)
    stroke(0, 255, 0, 50)
    for i in range(int(SIZE / STEP)):
        line(-2000, i * STEP, 2000, i * STEP)
        line(i * STEP, -2000, i * STEP, 2000)
    pushMatrix()
    translate(50, height * 3 / 5)
    # translate(width / 2, height * 3 / 4)
    noFill()
    stroke(0, 255, 0)
    line(-2000, 0, 2000, 0)
    line(0, -2000, 0, 2000)
    # グラフを描く
    count = min(frameCount - 1, len(points_list[0]) * 2)
    for j, points in enumerate(points_list):
        stroke(colors[j][0], colors[j][1], colors[j][2])
        for i in range(count):
            x1 = radians(i) * STEP * 2
            y1 = points[i % len(points)] * RADIUS / 2.0
            x2 = radians(i + 1) * STEP * 2
            y2 = points[(i + 1) % len(points)] * RADIUS / 2.0
            line(x1, -y1, x2, -y2)
    popMatrix()


def fourier_sine_series(i, j):
    i = radians(i)
    f = 0
    for k in range(j):
        f += sin(i * (k * 2 + 1)) / float(k * 2 + 1)
    return f * 4 / PI
fourier series 3

矩形波は周期 $${2 \pi}$$ の段差がある関数です。

$$
f(x) = \left\{
\begin{array}{ll}
-1 & (-\pi < x \leqq 0)\\
1 & (0 < x < \pi)
\end{array}
\right.
$$

フーリエ正弦級数は、加える数列の数が多くなるほど矩形波に近づいていきます。ライム(明るい緑で 100項まで加えたもの)を見ると、$${x = \pi}$$ あたりの鋭いとげに気づきます。このとげは、ギブズ現象と呼ばれるもので、不連続点で発生することが多いそうです。

フーリエ級数④ 三角波

BACKGROUND_COLOR = color(0, 44, 77)
colors = [
    [255, 255, 255],  # white
    [255, 127, 0],  # orange
    [255, 0, 255],  # magenta
    [127, 127, 255],  # lightblue
    [255, 255, 0],  # yellow
    [0, 255, 0],  # lime
    [255, 127, 127],  # pink
    [127, 127, 127],  # gray
    [191, 191, 191],  # lightgray
    [0, 255, 255],  # cyan
    [127, 0, 127],  # purple
    [0, 0, 255],  # blue
    [127, 0, 0],  # brown
    [0, 127, 0],  # green
    [255, 0, 0],  # red
    [0, 0, 0],  # black
]
SIZE = 1000
STEP = 50
# STEP = 250
RADIUS = 300
points_list = []


def setup():
    size(SIZE, SIZE)

    # グラフのデータ
    for j in range(0, 5):
        points_list.append(
            [fourier_sine_series(i, j) for i in range(360)]
        )
    points_list.append(
        [fourier_sine_series(i, 100) for i in range(360)]
    )


def draw():
    background(BACKGROUND_COLOR)
    strokeWeight(1)
    stroke(0, 255, 0, 50)
    for i in range(int(SIZE / STEP)):
        line(-2000, i * STEP, 2000, i * STEP)
        line(i * STEP, -2000, i * STEP, 2000)
    pushMatrix()
    translate(50, height * 3 / 5)
    noFill()
    stroke(0, 255, 0)
    line(-2000, 0, 2000, 0)
    line(0, -2000, 0, 2000)
    # グラフを描く
    count = min(frameCount - 1, len(points_list[0]) * 2)
    for j, points in enumerate(points_list):
        stroke(colors[j][0], colors[j][1], colors[j][2])
        for i in range(count):
            x1 = radians(i) * STEP * 2
            y1 = points[i % len(points)] * RADIUS / 4.0
            x2 = radians(i + 1) * STEP * 2
            y2 = points[(i + 1) % len(points)] * RADIUS / 4.0
            line(x1, -y1, x2, -y2)
    popMatrix()


def fourier_sine_series(i, j):
    i = radians(i)
    f = 0
    for k in range(j):
        f += ((-1) ** k) * sin(i * (k * 2 + 1)) / float((k * 2 + 1) ** 2)
    return f * 8 / PI
fourier series 4

三角波は、元になる正弦関数に初めから近い形をしています。そのためフーリエ正弦関数での近似は、少ない数列の数で行うことができます。5項まで加えた黄色の線でほぼ近似できています。

フーリエ級数⑤ ノコギリ波

def fourier_sine_series(i, j):
    i = radians(i)
    f = 0
    for k in range(j):
        f += sin(i * (k + 1)) / float(k + 1)
    return f * 2
fourier series 5

最後は「ノコギリ波」です。これは $${\frac{sin{(nx)}}{n}}$$ を無限に加えていくことで近似できます。波が徐々に直線に収束していく様子は興味深いものです。
今回は「フーリエ級数のグラフ化」に挑戦しました。5種類のグラフはいずれも面白い結果が得られ、私は大満足です。読者の皆様も楽しんでいただけたでしょうか。


前の記事
Processing でグラフを描く⑥ バラ曲線とジェネラティブアート
次の記事
Processing でグラフを描く⑧ スピログラフ

その他のタイトルはこちら


この記事が気に入ったらサポートをしてみませんか?