Joy to the world

とある中小企業のしがない技術者でクリスチャンな人が書く日記。実はメビウス症候群当事者だったり、統合失調症のパートナーがいたりする。

Gemini CLIを使ってみた

AI界隈はGemini CLIで賑わっていますが、ご多分に漏れず自分もやってみた。

無料で使える!と喧伝されていますが、GoogleのAIサービスは個人用の場合大抵入力されたデータを学習に使う前提となっているので、今回はあえてGemini Code Assist Standardに課金して試してみた。

コーディングエージェントについては各社色々と出してはいるけど、どこも結構強気な価格設定でなかなか手を出せない感じの中、Gemini CLIについては無料、もしくは課金でも20ドル程度で使えるので早速課金してみた次第。

初期の頃はすぐにレートリミットが来てGemini Flashに戻ってしまうみたいな事があったが、課金が効いているのか落ち着いてきたのか分からないが、すぐにFlashに戻ってしまう問題は解決しているみたいだ。

早速何かを作ってもらおうと思ったが、特に何も思いつかないので、昔よく作ったFDTD法による電磁界解析のプログラムを作ってもらうことにした。

単純な問題を作ってもらっても面白くないので、今回はちょっとプログラムが難しい吸収境界条件を設定した二次元のシミュレーションを行ってもらった。 結果はGifファイルで出力することを希望したところ、特に問題なくアニメーション結果が出てきた(GIFをうまく貼れないので結果は割愛)。

ソースコードは下記の通り。ちゃんとクーラントの安定条件も設定してあるし、マクスウェルの方程式をYee格子を使って差分式にする箇所も多分合ってる。今回は二次元でのシミュレーションだったけど、この理解度なら多分3次元にしてもちゃんとシミュレーションできるだろう。

昔自分がほぼ一年かけて理解したFDTD法の基礎を、こうも簡単にスラスラ書けてしまうというのは単純に技術の発展だと喜んでいいものか。「FDTD時間領域差分法入門」という本を読んで、必死にYee格子やらマクスウェルの方程式の差分式やらを展開していた頃が懐かしい。

AIでできなかったことができるようになったという話は、今ではそこらじゅうで聞くようになったが、果たしてそれは本当に「できるようになった」と言えるのだろうか。AIが書いたコードを理解できない時点で、そのコードやプロダクトは自分が作ったと言えるのだろうか。

AI全盛期になればなるほど、エンジニアの存在意義というものが問われている気がする。

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

# --- シミュレーションパラメータ ---
# 空洞のサイズ (メートル)
CAVITY_X = 1.0
CAVITY_Y = 1.0

# 空間グリッドの分割数
NX = 100
NY = 100

# 空間ステップ
dx = CAVITY_X / NX
dy = CAVITY_Y / NY

# 時間ステップの数
# 時間ステップの数
TIME_STEPS = 700
# --- プロット設定 ---
PLOT_MIN = -0.002  # プロット下限値
PLOT_MAX = 0.002   # プロット上限値

# --- 物理定数 ---
c0 = 2.99792458e8  # 真空中の光速 (m/s)
mu0 = 4 * np.pi * 1e-7 # 真空の透磁率 (H/m)
epsilon0 = 1 / (mu0 * c0**2) # 真空の誘電率 (F/m)

# --- FDTDパラメータ ---
# Courantの安定条件を満たすように時間ステップを決定
dt = 1 / (c0 * np.sqrt(1/dx**2 + 1/dy**2))

# FDTD更新係数
c_ez = dt / epsilon0
c_hx = dt / mu0
c_hy = dt / mu0

# --- 電場・磁場の初期化 ---
# TMzモードを想定 (Ez, Hx, Hy)
ez = np.zeros((NX, NY))
ez_old = np.zeros((NX, NY)) # 吸収境界用に1ステップ前のEzを保存
hx = np.zeros((NX, NY - 1))
hy = np.zeros((NX - 1, NY))

# --- アニメーション設定 ---
fig = plt.figure(figsize=(8, 8))
ax = fig.add_subplot(1, 1, 1)
im = ax.imshow(np.zeros((NX, NY)), cmap='jet', vmin=PLOT_MIN, vmax=PLOT_MAX, animated=True)
ax.set_title("FDTD Simulation with Absorbing Boundary (Ez)")
ax.set_xlabel("X")
ax.set_ylabel("Y")
fig.colorbar(im, ax=ax)

# --- 吸収境界用の係数 ---
c_abc = (c0 * dt - dx) / (c0 * dt + dx) # dx=dyと仮定

# --- シミュレーションループ ---
def update(frame):
    """各時間ステップでの更新関数"""
    global ez, ez_old, hx, hy

    # 1. 磁場(H)の更新
    hx[0:NX, 0:NY-1] = hx[0:NX, 0:NY-1] - c_hx / dy * (ez[0:NX, 1:NY] - ez[0:NX, 0:NY-1])
    hy[0:NX-1, 0:NY] = hy[0:NX-1, 0:NY] + c_hy / dx * (ez[1:NX, 0:NY] - ez[0:NX-1, 0:NY])

    # 2. 電場(E)の更新
    # ez_oldに現在のezを保存
    ez_old = ez.copy()

    ez[1:NX-1, 1:NY-1] = ez[1:NX-1, 1:NY-1] + c_ez * (
        (hy[1:NX-1, 1:NY-1] - hy[0:NX-2, 1:NY-1]) / dx -
        (hx[1:NX-1, 1:NY-1] - hx[1:NX-1, 0:NY-2]) / dy
    )

    # 3. 波源の導入 (ガウシアンパルス)
    pulse = np.exp(-0.5 * ((30 - frame) / 8)**2) * 1e-2
    ez[NX // 2, NY // 2] += pulse

    # 4. 吸収境界条件 (Mur's 1st order ABC)
    # x=0, x=NX-1, y=0, y=NY-1 の境界
    ez[0, 1:-1] = ez_old[1, 1:-1] + c_abc * (ez[1, 1:-1] - ez_old[0, 1:-1])
    ez[NX-1, 1:-1] = ez_old[NX-2, 1:-1] + c_abc * (ez[NX-2, 1:-1] - ez_old[NX-1, 1:-1])
    ez[1:-1, 0] = ez_old[1:-1, 1] + c_abc * (ez[1:-1, 1] - ez_old[1:-1, 0])
    ez[1:-1, NY-1] = ez_old[1:-1, NY-2] + c_abc * (ez[1:-1, NY-2] - ez_old[1:-1, NY-1])
    # コーナーは単純な平均で近似
    ez[0, 0] = (ez[0, 1] + ez[1, 0]) / 2
    ez[0, NY-1] = (ez[0, NY-2] + ez[1, NY-1]) / 2
    ez[NX-1, 0] = (ez[NX-2, 0] + ez[NX-1, 1]) / 2
    ez[NX-1, NY-1] = (ez[NX-2, NY-1] + ez[NX-1, NY-2]) / 2

    # 5. プロットの更新
    im.set_array(ez.T) # 描画のために転置
    if frame % 10 == 0:
        print(f"Step: {frame}/{TIME_STEPS}")
    return im,

# --- アニメーションの生成と保存 ---
print("Starting FDTD simulation and animation generation...")
ani = animation.FuncAnimation(fig, update, frames=TIME_STEPS, interval=20, blit=True)

# GIFとして保存
try:
    ani.save('electromagnetic_wave.gif', writer='imagemagick', fps=30)
    print("Animation saved as electromagnetic_wave.gif")
except Exception as e:
    print(f"Error saving animation: {e}")
    print("Please make sure you have ImageMagick installed and configured for Matplotlib.")
    print("Alternatively, you can display the animation directly.")
    # plt.show() # ImageMagickがない場合はこちらをコメントアウト解除して表示

plt.close()