Google Colabで始めるFreeFEM入門:Poisson方程式,誤差評価,log-logプロット

Introduction

有限要素法の数値計算を学ぶとき,最初の題材として Poisson 方程式はとても扱いやすいです.関数空間を作り,線形偏微分方程式を解くという基本の流れを身に付けることが出来ます.このページでは,Google Colab で実際にノートブックを作成し,FreeFEM を導入し,Poisson 方程式を解き,絶対誤差・相対誤差・log-log プロットまで確認する過程をまとめます.

対象とする Poisson 問題は

\[ -\Delta u = f \quad \text{in } \Omega := (0,1)^2, \qquad u=0 \quad \text{on } \partial\Omega \]

です.厳密解として

\[ u(x,y)=\sin(\pi x)\sin(\pi y) \]

を用い,

\[ f=2\pi^2u \]

を右辺として与えます.この設定により,相対誤差

\[ \frac{|u-u_h|_{H^1}}{|u|_{H^1}}, \quad \frac{\|u-u_h\|_{L^2}}{\|u\|_{L^2}} \]

を確かめます.

Google Colab での実行手順

まずは Google Colab をブラウザで開きます.新しいノートブックを作成し,タイトルをたとえば freefem_poisson_error_demo.ipynb のように付けます.

今回の例では GPU は不要です.標準の CPU ランタイムで十分です.セルを上から順に実行していけば動くようにしてあります.

セル 1:FreeFEM の導入

最初のセルは,Colab 上に FreeFEM を導入し,最後に FreeFem++ -v で動作確認をするものです.

%%bash
set -e

apt-get update -y
apt-get install -y wget ca-certificates \
  libgsl-dev libhdf5-dev liblapack-dev libopenmpi-dev freeglut3-dev

source /etc/os-release
echo "Ubuntu version: $VERSION_ID"

if [ "$VERSION_ID" = "24.04" ]; then
  URL="https://github.com/FreeFem/FreeFem-sources/releases/download/v4.15/FreeFEM-4.15-amd64-ubuntu24.04.deb"
elif [ "$VERSION_ID" = "22.04" ]; then
  URL="https://github.com/FreeFem/FreeFem-sources/releases/download/v4.15/FreeFEM-4.15-amd64-ubuntu22.04.deb"
else
  echo "Unsupported Ubuntu version in this Colab runtime: $VERSION_ID"
  exit 1
fi

wget -q "$URL" -O /tmp/freefem.deb
dpkg -i /tmp/freefem.deb || apt-get -f install -y

echo "Installed binaries:"
which FreeFem++ || true
which FreeFem++-nw || true
ls -l /usr/local/bin/FreeFem* || true

このセルの役割は環境構築だけです.もしこの段階で止まるなら,依存ライブラリや Ubuntu の版の問題です.逆に,ここが通れば,次は FreeFEM 本体が実際に動くかどうかを小さな例で確かめればよいことになります.

Google Colab で FreeFEM を使うときの注意

Google Colab は手軽に計算実験を始められる反面,通常のローカル環境とは少し違う注意が必要です.特に FreeFEM を使う場合は,最初から「一時的な実行環境である」ことを前提にノートブックを組んでおくと,途中で動かなくなったときの原因を切り分けやすくなります.

第一に,Colab ではセッションごとに環境が初期化されることがあります.そのため,FreeFEM のインストールセルは毎回上から実行する前提で用意しておく方が安全です.また,環境構築と動作確認を別セルに分けておくと,どこで問題が起きたのかを追いやすくなります.

第二に,Colab は headless 環境なので,FreeFEM の実行には FreeFem++-nw を使うのが基本です.通常の GUI 前提の実行ではなく,no-window 版で計算を回す形にしておくと安定しやすくなります.

第三に,Colab の Ubuntu バージョンは固定とは限りません.そのため,インストール時には /etc/os-release を読んでランタイムの版を確認し,対応する .deb パッケージを選ぶ形にしておくのが安全です.実際に 22.04 と 24.04 で URL を切り替える形にしておくと,環境変化に対応しやすくなります.

第四に,各セルはできるだけ自立させておく方が扱いやすいです.たとえば mkdir -p freefem_demo のような作業用フォルダー作成を,必要なセルごとに入れておくと,あとからセル単体で再実行しても壊れにくくなります.Colab では,ノートブック全体を最初から順に流すとは限らないからです.

第五に,可視化は FreeFEM だけで完結させようとするより,FreeFEM 側で数値データを書き出し,Python 側で図を描く方が見通しがよいです.たとえば FreeFEM で .txt にサンプル値を書き出し,Colab の Python セルで NumPy と Matplotlib を用いて等高線図や曲面図を描く流れにすると,headless 環境でも扱いやすくなります.

要するに,Google Colab 上で FreeFEM を使うときは,「毎回環境を作り直す前提で,FreeFem++-nw を使い,Ubuntu 版を確認し,セルを自立させ,出力はファイル保存して Python で図示する」という方針にしておくのが実用的です.関連する補足は,Google Colab で FreeFEM を使うときの別記事 にも整理しています.

セル 2:Poisson 方程式を解いて誤差を測る

次が本体セルです.FreeFEM では,変分問題を problem で書き,int2d によって積分を書き下ろせます.ここでは,鈴木厚「FreeFEM入門 - 非線形弾性体ソルバーを例に」の5ページにある誤差評価の方針に合わせて,厳密解とその偏微分を func で与え,(u_h-sol)^2(dx(u_h)-solx)^2+(dy(u_h)-soly)^2 を直接積分して誤差を測ります.このセルでは,粗い格子から細かい格子まで順番に解き,誤差を errors.csv に書き出します.さらに,可視化用の解を uh_vis.vtu として保存します.

%%bash
cat > poisson_error_demo.edp <<'EOF'
load "iovtk"

real pi2 = pi*pi;
int[int] Ns = [8, 16, 32, 64, 128];
ofstream ferr("errors.csv");
ferr << "N,h,absL2,relL2,absH1semi,relH1semi,absH1\n";

func sol  = sin(pi*x)*sin(pi*y);
func solx = pi*cos(pi*x)*sin(pi*y);
func soly = pi*sin(pi*x)*cos(pi*y);
func rhs  = 2*pi2*sin(pi*x)*sin(pi*y);

for (int k = 0; k < Ns.n; ++k) {
  int N = Ns[k];
  mesh Th = square(N, N);

  fespace Vh(Th, P1);
  Vh uh, v;

  problem Poisson(uh, v, solver=LU)
      = int2d(Th)( dx(uh)*dx(v) + dy(uh)*dy(v) )
      - int2d(Th)( rhs*v )
      + on(1, 2, 3, 4, uh=0);

  Poisson;

  real absL2 = sqrt(int2d(Th, qforder=10)((uh - sol)^2));
  real absH1semi = sqrt(int2d(Th, qforder=10)(
        (dx(uh) - solx)^2
      + (dy(uh) - soly)^2
  ));
  real absH1 = sqrt(int2d(Th, qforder=10)(
        (uh - sol)^2
      + (dx(uh) - solx)^2
      + (dy(uh) - soly)^2
  ));

  real normL2 = sqrt(int2d(Th, qforder=10)(sol^2));
  real seminormH1 = sqrt(int2d(Th, qforder=10)(solx^2 + soly^2));
  real relL2 = absL2 / normL2;
  real relH1semi = absH1semi / seminormH1;
  real h = sqrt(2.0) / N;

  cout << "N = " << N
       << ", h = " << h
       << ", absL2 = " << absL2
       << ", relL2 = " << relL2
       << ", absH1semi = " << absH1semi
       << ", relH1semi = " << relH1semi << endl;

  ferr << N << ","
       << h << ","
       << absL2 << ","
       << relL2 << ","
       << absH1semi << ","
       << relH1semi << ","
       << absH1 << "\n";
}

int Nvis = 16;
mesh ThVis = square(Nvis, Nvis);

fespace VhVis(ThVis, P1);
VhVis uhVis, vVis;

problem PoissonVis(uhVis, vVis, solver=LU)
    = int2d(ThVis)( dx(uhVis)*dx(vVis) + dy(uhVis)*dy(vVis) )
    - int2d(ThVis)( rhs*vVis )
    + on(1, 2, 3, 4, uhVis=0);

PoissonVis;

int[int] order = [1];
string dataName = "uh";
savevtk("uh_vis.vtu", ThVis, uhVis, dataname=dataName, order=order);
EOF

FreeFem++-nw poisson_error_demo.edp

このセルの要点は次の通りです.

  1. func sol, func solx, func soly により,厳密解とその偏微分を有限要素空間に補間せずに与えます.
  2. square(N,N) により単位正方形の三角形分割を作ります.
  3. fespace Vh(Th,P1) により 1 次 Lagrange 有限要素空間を定義します.
  4. problem Poisson(uh,v,...) により,
\[ \int_\Omega \nabla u_h\cdot\nabla v\,dx = \int_\Omega f v\,dx \]

をそのまま書きます.
4 辺すべてには on(1,2,3,4,uh=0) により同次 Dirichlet 条件を入れます.
誤差評価では,int2d(Th, qforder=10) を用いて \(L^2\) 誤差,\(H^1\) 半ノルム誤差,さらに参考として \(H^1\) ノルム誤差も計算します.ここで h = \sqrt{2}/N としているのは,正方形を対角線で切った各三角形の代表長さを念頭に置いているためです.

ここでは \(H^1\) 誤差を測るときに,有限要素空間への補間との比較ではなく,厳密解の勾配

\[ \partial_x u = \pi\cos(\pi x)\sin(\pi y), \quad \partial_y u = \pi\sin(\pi x)\cos(\pi y) \]

を直接使っています.したがって,測っているのは真の \(|u-u_h|_{H^1}\) 半ノルム誤差です.この書き方は,鈴木厚の講義資料 5 ページの流れに沿っています.

セル 3:収束率と log-log プロット

次に,FreeFEM が書き出した errors.csv を Python で読み込み,実験的収束率と log-log プロットを作ります.元の記事と同じく,絶対誤差と相対誤差を並べて見ます.

import numpy as np
import matplotlib.pyplot as plt

# CSV を読み込みます
raw = np.genfromtxt("errors.csv", delimiter=",", names=True)

Ns = raw["N"]
hs = raw["h"]
abs_L2s = raw["absL2"]
rel_L2s = raw["relL2"]
abs_H1s = raw["absH1semi"]
rel_H1s = raw["relH1semi"]

def rates(errors, hs):
    return np.log(errors[1:] / errors[:-1]) / np.log(hs[1:] / hs[:-1])

print("L2 absolute rates  =", rates(abs_L2s, hs))
print("L2 relative rates  =", rates(rel_L2s, hs))
print("H1 absolute rates  =", rates(abs_H1s, hs))
print("H1 relative rates  =", rates(rel_H1s, hs))

plt.figure(figsize=(6, 5))
plt.loglog(hs, abs_L2s, "o-", label="absolute L2 error")
plt.loglog(hs, rel_L2s, "s--", label="relative L2 error")
plt.xlabel("h")
plt.ylabel("error")
plt.title("L2 errors on a log-log plot")
plt.grid(True, which="both")
plt.legend()
plt.show()

plt.figure(figsize=(6, 5))
plt.loglog(hs, abs_H1s, "o-", label="absolute H1 seminorm error")
plt.loglog(hs, rel_H1s, "s--", label="relative H1 seminorm error")
plt.xlabel("h")
plt.ylabel("error")
plt.title("H1 seminorm errors on a log-log plot")
plt.grid(True, which="both")
plt.legend()
plt.show()

このセルを実行すると,\(L^2\) 誤差の収束率と \(H^1\) 半ノルム誤差の収束率が表示されます.ここでは errors.csv の列名を absH1semi, relH1semi として,半ノルム誤差を明示しています.滑らかな厳密解に対する P1 要素なので,理論的には

\[ |u-u_h|_{H^1}=O(h), \quad \|u-u_h\|_{L^2}=O(h^2) \]

が期待されます.したがって,出力が H1 で 1 に近く,L2 で 2 に近ければ,標準的理論と整合的に動いていると読めます.

セル 4:2次元メッシュ図と3次元近似解の可視化

FreeFEM には plot による可視化がありますが,Colab では GUI を直接扱いにくいので,ここでは savevtk で出力した uh_vis.vtu を Python 側で読み込んで表示します.

import meshio
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D  # noqa: F401

m = meshio.read("uh_vis.vtu")
points = m.points[:, :2]

triangles = None
for cell_block in m.cells:
    if cell_block.type == "triangle":
        triangles = cell_block.data
        break

if triangles is None:
    raise RuntimeError("triangle cells were not found in uh_vis.vtu")

if "uh" in m.point_data:
    values = np.asarray(m.point_data["uh"]).reshape(-1)
else:
    raise RuntimeError("point data 'uh' was not found in uh_vis.vtu")

plt.figure(figsize=(6, 6))
plt.triplot(points[:, 0], points[:, 1], triangles, linewidth=0.8)
plt.xlabel("x")
plt.ylabel("y")
plt.title("2D mesh")
plt.gca().set_aspect("equal")
plt.show()

plt.figure(figsize=(6, 5))
plt.tricontourf(points[:, 0], points[:, 1], triangles, values, levels=20)
plt.colorbar(label="u_h")
plt.xlabel("x")
plt.ylabel("y")
plt.title("2D contour of FEM solution")
plt.gca().set_aspect("equal")
plt.show()

fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection="3d")
ax.plot_trisurf(points[:, 0], points[:, 1], values, triangles=triangles,
                linewidth=0.2, antialiased=True)
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("u_h")
ax.set_title("3D surface of FEM solution")
plt.show()

このセルを実行すると,まず三角形分割そのものが見えます.次に,近似解の等高線図から,中心付近で値が大きく,境界で0になっている様子が確認できます.最後の3次元図では,

\[ u_h(x,y)\approx \sin(\pi x)\sin(\pi y) \]

の山型の形が視覚的に分かります.

メッシュの切り方について

FreeFEM の square コマンドでは,標準の三角形分割を簡単に作れます.また,ドキュメントには flags=i を変えることで複数の三角形分割パターンを試す例もあります.したがって,導入段階ではまず標準の square(N,N) を使い,その後に対角線の入れ方の違いを比較する,という流れが自然です.

今回のページでは,まず Poisson 方程式,誤差評価,収束率という有限要素法の本体に集中するため,もっとも素直な square(N,N) を採用しました.

絶対誤差と相対誤差

今回のコードでは,まず絶対誤差

\[ \|u-u_h\|_{L^2}, \quad |u-u_h|_{H^1} \]

を計算しています.これは,数値解が厳密解からどれだけ離れているかをそのまま測る量です.

そのうえで,相対誤差

\[ \frac{\|u-u_h\|_{L^2}}{\|u\|_{L^2}}, \quad \frac{|u-u_h|_{H^1}}{|u|_{H^1}} \]

も計算しています.こちらは,解そのものの大きさに対してどれだけずれているかを見る量です.

今回のように厳密解を固定してメッシュだけ細かくする実験では,相対誤差は絶対誤差を定数で割っただけなので,収束次数そのものは同じになります.それでも相対誤差を出す理由ははっきりあります.問題ごとに解の大きさが違っていても比較しやすくなりますし,「誤差の大きさ」と「解そのものの大きさ」を区別して考える練習にもなるからです.

log-log プロットは何を見せているのか

log-log プロットを使う理由は,誤差の減り方を「目で見てすぐ分かる形」に直したいからです.有限要素法では,誤差はしばしば

\[ E(h) \approx C h^r \]

のようなべき乗則で減ります.両辺の対数を取ると

\[ \log E(h)=\log C + r\log h \]

となるので,両軸を対数目盛にすると,点は直線に並びます.そして,その傾きが収束次数 \(r\) です.このため,log-log プロットは「有限要素法が期待どおりの次数で収束しているか」を一目で確かめるための道具としてとても便利です.

References

  • 鈴木厚,RIKEN HPC サマースクール 2023 - Society 5.0 に向けて - FreeFEM 入門 - 非線形弾性体ソルバーを例に,2023年9月7日.特に 5 ページ「誤差評価を確認する FreeFEM スクリプト」,8 ページ「func と有限要素データの違い」を参考にしました.
  • FreeFEM documentation: Installation guide
  • FreeFEM documentation: Download
  • FreeFEM documentation: Getting started / Poisson
  • FreeFEM documentation: Visualization
  • FreeFEM documentation: savevtk
  • Matplotlib documentation: pyplot.loglog
  • Hiroki Ishizaka, Google Colab で FreeFEM を使うときの注意に関する記事, 2026年4月20日.Colab 上でのセッション初期化,FreeFem++-nw の使用,Ubuntu 版確認,セルの自立性,Python 側での可視化という実務上の注意点を参照しました.

ページの先頭へ