このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

R言語で複素関数の可視化

昨日の「R言語メタプログラミングのサンプル」に実例として出したRプログラムを見ているうちに、これを手直しして複素関数の可視化が出来るんじゃないか、と思いました。

次元を落として3Dグラフを描く方法と、平面から平面への写像を模様として描く方法を試してみました。

内容:

  1. 複素関数
  2. 可視化の方法
  3. 実例
  4. 感想など

複素関数

C複素数の全体(ガウス平面)とします。D⊆Cを適当な領域として、f:D→C という写像複素関数ですが、単なる写像ではなくて微分可能(正則)なものを「複素関数」と呼ぶのが普通です。

古典的な記法では、複素関数を w = f(z) (z∈D)のように書きます。虚数単位をiとして、z = x + yi、w = u + vi のような書き方も習慣化しています。習慣に従い、z, w, x, y, u, v は今説明した意味で使うことにします。虚数単位を太字にしたのは、iをインデックスとしても使うからです。

複素関数を可視化する際に、定義域D内に、閉区間の直積となるような矩形 [a, b]×[c, d] を取ります。つまり、[a, b]×[c, d] ⊆ D。さらに、区間[a, b]、[c, d]を、それぞれN等分、M等分して、実数値の増加列を作ります。

  • a = x1 < x2 < ... < xN+1 = b
  • c = y1 < y2 < ... < yM+1 = d

zi,j := xi + yji と定義すると、zi,j は、(N + 1)行(M + 1)列の行列とみなすことができます。あるいは、(N + 1)×(M + 1)個の複素数からなる集合ともみなせます。集合とみなすときはZという記号を使うことにします。つまり、Z := {zi,j | i = 1, 2, ..., N+1、j = 1, 2, ..., M+1}。

Z⊆D なので、zi,j∈Z に対して wi,j := f(zi,j) を定義できます。図示の対象となるのは、w平面の点達 wi,j (i = 1, 2, ..., N+1、j = 1, 2, ..., M+1)です。

可視化の方法

w = f(z) の可視化の方法は2種類試してみました。

  1. z平面の方眼(格子)の像を、w平面における模様として描く。
  2. wの値から単一実数を取り出して、3Dグラフを描く。

ニ番目の3Dグラフの方法では、複素数wから実数の取り出し方が4種類あるので、細分すると5種類の描き方があります。

一番目の方法は仮に写像図と呼んでますが、この方法が描画関数のデフォルトで、drawComplex(z^2) とすると次の図を描きます。

これは、z平面にあった次のようは方眼(網目)がw平面に写って描く模様です。

z平面内における縦横の直線の交点(格子点)が、先に説明した集合Zです。w平面の点達f(Z)と曲線群による模様が w = f(z) の写像図です。

3Dグラフのほうは、Re(f(z)), Im(f(z)), Mod(f(z)), Arg(f(z)) の4種の値を3Dグラフに描きます。それぞれの意味は:

  1. f(z) の実部
  2. f(z) の虚部
  3. f(z) の絶対値
  4. f(z) の偏角

実例

w = z^3 (3次関数)と w = sqrt(z) (平方根関数)を描いた図を示します。drawComplex(関数の式, 描画方法, xdiv = 80, ydiv = 80) を呼び出した結果です。デフォルトの区間[-1, 1]を80等分しています。4つの3Dグラフは、左上:実部、右上:虚部、左下:絶対値、左下:偏角 です。

3次関数 w = z^3


平方根関数 w = sqrt(z)


感想など

ワンライナーですぐに複素関数を図示できるので、なかなか便利です。「えっ、この関数はこんな形だったの」と意外だったりして、面白いです。それでも、2次元→2次元 の写像をイメージするのは難しいですね。

Rは組み込みのデータ型として複素数をサポートしているので、複素関数を扱うのはとても簡単です。何も意識しなくていいと言っていいでしょう。加減乗除はもちろん、指数関数、対数関数、三角関数、逆三角関数双曲線関数などは、複素数を引数にしてちゃんと動きます。これは素晴らしい。

惜しむらくは、Rにアニメーション機能が備わってないことです。ライブラリを使ってアニメーション出来なくはない(「Rで離散力学系のアニメーションを作ってみた」参照)ですが、面倒で機能貧弱です。時間方向が使えると、表現の幅が格段に拡がるのですが。


以下にソースコード、「R言語メタプログラミングの基礎」で定義した2つのユーティリティ関数noDefault()とmakeFunction()が必要です。

[追記]ソースコードはgistにも貼り付けてあります。

以下のコードは変更する気はありませんが、gistのほうは変更するかも知れません。[/追記]

# drawComplex.R

# gridパッケージが必要
if (!require('grid')) {
  install.packages('grid')
  stopifnot(require('grid'))
}

# noDefaultとmakeFunctionが必要
stopifnot(exists("noDefault") && is.function(noDefault))
stopifnot(exists("makeFunction") && is.function(makeFunction))

# 複素関数の値(wの値)を成分とする複素数マトリックスを作成し、関連情報と共に返す
makeXyzw <-
  function (
    # 複素関数の式、コールオブジェクトを渡す
    expr,
    # 複素変数の名前、文字列で指定する
    varname = "z",
    # 複素変数のガウス平面(x-y-座標)における描画対象域
    xlim = c(-1, 1), ylim = c(-1, 1),
    # 描画対象域のx,y方向を幾つの少区間に分割するか(分割数)
    xdiv = 20, ydiv = 20)
{
  x0 <- xlim[1]
  x1 <- xlim[2]
  y0 <- ylim[1]
  y1 <- ylim[2]
  xStep <- (x1 - x0)/xdiv
  yStep <- (y1 - y0)/ydiv

  xBreaks <- seq(from = x0, to = x1, by = xStep)
  yBreaks <- seq(from = y0, to = y1, by = yStep)

  # 関数の仮引数リストの作成
  fplist <- list(noDefault()) # デフォルト値なし1変数
  names(fplist) <- c(varname)  # 引数名(複素独立変数名)を指定
  # 関数の作成
  fun <- makeFunction(fplist, expr) 

  wMatrix <- matrix(NA, xdiv + 1, ydiv + 1)
  for (i in 1:(xdiv + 1)) {
    for (j in 1:(ydiv + 1)) {
      wMatrix[i, j] <- fun(xBreaks[i] + yBreaks[j]*1i)
    }
  }
  # 参考のために独立変数zのマトリックスも添える
  zMatrix <- outer(xBreaks, yBreaks, function(x, y) x + y*1i)
  list(
      xlim = xlim,
      ylim = ylim,
      xStep = xStep,
      yStep = yStep,
      x = xBreaks,
      y = yBreaks,
      z = zMatrix,
      w = wMatrix
      )
}  

# 複素関数を描く
# 描画方法:"map", "re", "im", "mod", "arg"
# map : 写像図、x方向/y方向の直線群に対する像曲線群を描く
# re : 値の実部を描く
# im : 値の虚部を描く
# mod : 値の絶対値を描く
# arg : 値の偏角を描く
drawComplex <-
  function (
    # 複素関数の式、引数としてzの式を書く
    expr,
    # 描画方法、シンボルでもよい
    way = "map",
    # 複素変数のガウス平面(x-y-座標)における描画対象域
    xlim = c(-1, 1), ylim = c(-1, 1),
    # 描画対象域のx,y方向を幾つの少区間に分割するか(分割数)
    xdiv = 20, ydiv = 20,
    # perspのオプション
    theta = 30, phi = 30, expand = 0.5, col = "skyblue",
    # 関数の情報を戻り値として出力するか
    output.info = FALSE
    )
{
  # 引数の式を評価せずに取り出す
  expr <- substitute(expr)
  # way引数の取得とチェック
  way <- substitute(way)
  stopifnot(is.name(way) || is.character(way))
  way <- as.character(way)
  stopifnot(any(c("map", "re", "im", "mod", "arg") == way))
  
  # 関数値のマトリックスを作成する
  xyzw  <- makeXyzw(expr,
                    varname = "z",
                    xlim = xlim, ylim = ylim,
                    xdiv = xdiv, ydiv = ydiv
                    )
  # 基本的なパラメータをセット
  w <- xyzw$w
  ulim <- range(Re(xyzw$w))
  vlim <- range(Im(xyzw$w))
  rangeWidth <- ulim[2] - ulim[1]
  rangeHeight <- vlim[2] - vlim[1]
  rangeSize <- max(rangeWidth, rangeHeight)
  u0 <- ulim[1]
  v0 <- vlim[1]
  
  if (way == "map") {
    # 写像図による描画
    # 使用する変数: w, u0, v0, rangeSize, rangeWidth, rangeHeight
    uOffset <- (rangeSize - rangeWidth)/(2*rangeSize)
    vOffset <- (rangeSize - rangeHeight)/(2*rangeSize)
    
    grid.newpage()
    grid.rect(width = rangeWidth/rangeSize, height = rangeHeight/rangeSize)
    # x方向直線の像曲線
    for (j in 1:(ydiv + 1)) {
      u <- Re(w[ ,j])
      v <- Im(w[ ,j])
      grid.lines((u - u0)/rangeSize + uOffset, (v - v0)/rangeSize + vOffset , gp = gpar(col="red"))
    }
    # y方向直線の像曲線
    for (i in 1:(xdiv + 1)) {
      u <- Re(w[i, ])
      v <- Im(w[i, ])
      grid.lines((u - u0)/rangeSize + uOffset, (v - v0)/rangeSize + vOffset, gp = gpar(col="blue"))
    }
  } else {
    # perspによる3Dグラフ描画
    # 使用する変数: way, x, y, w, xlim, ylim
    funName <- paste(toupper(substr(way, 1, 1)), substr(way, 2, 10), sep="")
    fun <- eval(as.name(funName))

    # perspの引数リストを作成する
    args <- list(
      x = xyzw$x,
      y = xyzw$y,
      z = fun(xyzw$w),
      xlab = "x",
      ylab = "y",
      zlab = paste(funName, "(", deparse(expr), ")", sep=""),
      xlim = xlim,
      ylim = ylim,
      theta = theta,
      phi = phi,
      expand = expand,
      col = col
      )
    # persp関数の呼び出し
    do.call("persp", args)
  }
  # 情報の出力(オプショナル)
  if (output.info) {
    return(
      list (
        xlim = xlim,
        ylim = ylim,
        ulim = ulim,
        vlim = vlim,
        rangeRect = list(
          left = u0,
          bottom = v0,
          width = rangeWidth, 
          height = rangeHeight
          )
        )
      )
  }
}