数学の最近のブログ記事

山口昌哉、「カオスとフラクタル」講談社ブルーバックスをきちんと読み直してみると、個別の図形の計算はポール・ピエール・レヴィだとしても、複数個の縮小写像による自己相似集合という表し方を発見したのは、日本では、京都大学理学部の畑 政義氏が最初ということである。現在、京都大学理学研究科/数学・数理解析専攻解析学講座/准教授という肩書である。

さて、レヴィの図形と同じ式で、定数a, b, c, dだけ違う他の図形も示しておく。

  1. a = 0, b = 0.4+0.5i, c = 0, d = 0.4+0.5i
  2. a = 0.4614+0.4614i, b = 0, c = 0.622-0.192i, d = 0
  3. a = 0, b = 0.5+0.2887i, c = 0, d = 0.6667
  4. a = 0.707i, b = 0, c = 0.5, d = 0
  5. a = 0.4614+0.4614i, b = 0, c = 0, d = 0.2896-0.585i

これを見ていると、1. は悪魔系、2.は雲、3.は木の枝、4.は氷柱、5.は海藻または細菌のコロニーと、なかなか表現力豊かである。そういえば、レヴィの図形は蟹か。

フラクタルについては、シェルピンスキのガスケットコッホ曲線マンデルブロ集合ジュリア集合など、典型的なものを描いてみたが、そういえば、少し毛色の変わったフラクタルであるレヴィの図形はまだ描いていないね、と思って、一時はバイブルのように読んだ、山口昌哉、「カオスとフラクタル」講談社ブルーバックスを自分の書棚で探したが、どうしても見つからない。あれっ、いくらなんでもこの本は捨てたり古書店に売ったりしていないはずだが、と思ったが、いくら探しても見つからないので、仕方なくアマゾンの古本で買いなおした。そして、レヴィの図形が載っているページをみてみた。

レヴィの図形は、描き方は、再帰的に所定の操作を繰り返す点でシェルピンスキのガスケットに似ているが、これは一応、シェルピンスキのガスケットとは違って、複素力学系ということになるのだろう。

そのために以下のような線形の複素関数f0(z)とf1(z)を用意する。

ここで、zは複素変数であり、a, b, c, dは複素定数である。そこで、a, b, c, dとして適当な値を選び、Kを複素点の集合とする。

そこで、Kの初期値として、K ={i}、すなわち単一の元iだけを含む集合とする。そこで、K ← f0(K) ∪ f1(K)という操作を複数回行うものとする。一回の操作で点の数は倍になるので、n回繰り返すと、点の数は2nになる。これらの点を複素平面上でプロットするのであるが、特にレヴィの図形の場合、a = 0.5+0.5i, b = 0, c = 0.5-0.5i, d = 0である。下記のような図形となる。

なお、レヴィとは、フルネームがポール・ピエール・レヴィ、フランスの数学者であり、確率論に優れた業績を残した。上記の図形を研究し手書きで描いたりしたそうである。

なお、例によって、プログラムのソースコードも付ける。

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Main.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
void fracta(double x, double y, int level);

// 定数の設定
double ar = 0.5;  // aの実部
double ai = 0.5;  // aの虚部
double br = 0.0;  // bの実部
double bi = 0.0;  // bの虚部
double cr = 0.5;  // cの実部
double ci = -0.5;  // cの虚部
double dr = 0.0;  // dの実部
double di = 0.0;  // dの虚部

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
	: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 fracta(0.0, 1.0, 0);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 Close();
}
//---------------------------------------------------------------------------

void fracta(double x, double y, int level)  {
 int Level = level;
 double px, py, tx, ty;
 if (level > 15 )  {
   px = 200.0 + 300.0 * x;
   py = 400.0 - 300.0 * y;
   Form1->Canvas->Pixels[px][py] = clBlack;
   return;
 }
 Level++;
 tx = (ar * x - ai * y + br * x + bi * y);
 ty = (ai * x + ar * y - br * y + bi * x );
 fracta(tx, ty, Level);
 tx = (cr * (x - 1.0) - ci * y + dr * (x - 1.0) + di * y + 1.0);
 ty = (ci * (x - 1.0) + cr * y + di * (x - 1.0) - dr * y );
 fracta(tx, ty, Level);
 return;
}

シェルピンスキのカーペットの変形に続いて、コッホ曲線も変形版を描いてみることにした。

すなわち、直線を三等分した後、中央の2点から垂線をそれぞれ引いて、垂線の先端の点同士に線を引く。以下のような感じである。

以上のような操作を結果の5つの線分に再帰的に適用する、ということを繰り返すと、以下のような図形になる。

どんな図形かと思っていたら、これはこれで、そんなに違和感は与えないような気がする。

ちなみに、この図形のフラクタル次元はlog(5)/log(3) = 1.464974...だから、たまたまシェルピンスキの市松模様と同じである。コッホ曲線のフラクタル次元はlog(4)/log(3) = 1.261859...なので、少なくともコッホ曲線よりは次元は高い。

なお、プログラムのソースコードは、以下のとおりである。

//---------------------------------------------------------------------------

#include <vcl.h>
#include <math.h>
#pragma hdrstop

#include "Main.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
const double pi = 3.14159;
void fracta(double x1, double y1, double x2, double y2, int level);
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
	: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
   Close();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 double x1, y1, x2, y2;
 Canvas->Pen->Color = clBlack;
 x1 = 30.0;  y1 = 400.0;
 x2 = 800.0; y2 = 400.0;
 fracta(x1,y1,x2,y2,0);
}
//---------------------------------------------------------------------------

void fracta(double x1, double y1, double x2, double y2, int level)  {
 double X1,Y1, X2,Y2, X3,Y3, X4,Y4, X5,Y5, X6, Y6, TH, S;
int Level = level;
 Level++;
if ( Level > 9 )      {
  Form1->Canvas->MoveTo(x1,y1);
  Form1->Canvas->LineTo(x2,y2);
  return;
}

 X1 = x1; Y1 = y1; X2 = x2; Y2 = y2;
 X3 = (X2 - X1)/3.0 + X1;  Y3 = (Y2 - Y1)/3.0 + Y1;
 X4 = 2.0 * (X2 - X1)/3.0 + X1;  Y4 = 2.0 * (Y2 - Y1)/3.0 + Y1;
 S = sqrt((X4-X3)*(X4-X3) + (Y4-Y3)*(Y4-Y3));
 TH = acos((X4 - X3)/S);
 if ( Y4 - Y3 < 0 ) TH = -TH;
 X5 = X3 + S * cos(TH - pi/2.0);
 Y5 = Y3 + S * sin(TH - pi/2.0);
 X6 = X4 + S * cos(TH - pi/2.0);
 Y6 = Y4 + S * sin(TH - pi/2.0);
 fracta(X1, Y1, X3, Y3, Level);
 fracta(X3, Y3, X5, Y5, Level);
 fracta(X5, Y5, X6, Y6, Level);
 fracta(X6, Y6, X4, Y4, Level);
 fracta(X4, Y4, X2, Y2, Level);
 return;
}

フラクタル図形を本にあるとおりにただ描いてみるのもつまらないので、一番手頃そうなシェルピンスキのカーペットを少し変形してみることにした。

すなわち、シェルピンスキのカーペットでは、正方形を3×3に等分して、中央の小正方形を中抜きするのだが、3×3に等分した小正方形のうち、市松模様の小正方形だけを残し、あとは取り去るようにする、というだけの変形である。

これを描いてみると、以下のように、いかにもインパクトの弱い図形になってしまっているが、まあこれもありか、と思うことにする。

ちなみに、関連図形のフラクタル次元を示すと以下のとおりである。

  • シェルピンスキのガスケット: log(3)/log(2) = 1.584963...
  • シェルピンスキのカーペット: log(8)/log(3) = 1.892789...
  • シェルピンスキの市松模様?: log(5)/log(3) = 1.464974...
これをみるとなんと、市松模様はガスケットより次元が低いのだった。

//---------------------------------------------------------------------------
#include <vcl.h>
#include <math.h>
#pragma hdrstop

#include "Main.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
void Fracta(double p1x, double p1y, double p2x, double p2y, double p3x, double p3y,
 double p4x, double p4y,int level);
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
 Close();    
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 Canvas->Pen->Color = clBlack;
 double a = 20.0;
 double b = 20.0;
 double d = 400.0;
 // 初期位置のセット

 Fracta(a,b, a + d,b,  a + d, b + d, a, b + d,1); 
}
//---------------------------------------------------------------------------

void Fracta(double p1x, double p1y, double p2x, double p2y, double p3x, double p3y,
 double p4x, double p4y,int level)  {
 double a,b,d;
 int depth = level;
 if ( depth > 8 ) return;
 depth++;	

 d = (p3x - p1x)/3.0; 
 
 Form1->Canvas->MoveTo(p1x,p1y);
 Form1->Canvas->LineTo(p2x,p2y); 
 Form1->Canvas->LineTo(p3x,p3y);
 Form1->Canvas->LineTo(p4x,p4y);
 Form1->Canvas->LineTo(p1x,p1y);
 
 a = p1x;  b = p1y;
 Fracta(a,b,a + d,b,a + d, b + d, a, b + d, depth);
 
 a = p1x + 2.0 * d;  b = p1y;
 Fracta(a,b,a + d,b,a + d, b + d, a, b + d, depth);
 
 a = p1x + d;  b = p1y + d;
 Fracta(a,b,a + d,b,a + d, b + d, a, b + d, depth);
 
 a = p1x;  b = p1y + 2.0 * d;
 Fracta(a,b,a + d,b,a + d, b + d, a, b + d, depth);

 a = p1x + 2.0 * d;  b = p1y + 2.0 * d;
 Fracta(a,b,a + d,b,a + d, b + d, a, b + d, depth);
 return;
}

別にこれがトリというわけでもないが、マンデルブロ集合の後は、ジュリア集合を描いてみる。

それで、ともかくとりあえず描いてみたジュリア集合の画像と、そのプログラムを示す。


すなわち、ジュリア集合は、f(z) = z2 + cをもちいた複素力学系で、複素定数cをあらかじめ決めておき、初期値z0をいろいろ変えてみるものであり、すると、どういうcを選ぶかが、審美的な画像になるかどうかの決め手なのであるが、妥当な複素数cをどう決めるか私には難しいので、数学セミナーの荒井迅氏の記事にあった、c = 0.36237 + 0.32iという定数を拝借させて頂いた。この図形は、どこかしらドラゴンというか、八岐大蛇というか、中国の水墨画のようで、なかなか趣がある。

ただこれだと、画像の解像度が低いことで、細部のフラクタル性がわからない。なので、プログラムの下に、ズームした画像も貼り付けてみた。
なお、ズームとは下記のコードで、x = (double) A/180; y = (double) B/180;となっている分母の180を適当に増やし、px = 420 + A; py = 280 + B;となっている420と280を適当に変更して、みたい領域が画面に出てくるようにすることである。

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Main.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
	: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 Close();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
double x,y,tx,ty,x0,y0;
double a = 0.36237;
double b = 0.32;
int n,A,B;
int px, py;

 for ( A = -200; A < 200; A++ )     {
   for ( B = -230; B < 230; B++ )  {
	 x = (double) A/180; y = (double) B/180;
	 n = 0;
     while ( x*x + y*y < 4.0 )   {
      tx = x*x - y*y + a;
      ty = 2.0*x*y + b;
      x = tx;
      y = ty;
      n++;
      if ( n > 2000 ) break;
    }
    px = 420 + A; py = 280 + B;
    if ( n > 2000 ) Canvas->Pixels[px][py] = clBlack;
    else if ( n == 1 ) Canvas->Pixels[px][py] = clGreen;
    else if ( n == 2 ) Canvas->Pixels[px][py] = clYellow;
    else if ( n == 3 ) Canvas->Pixels[px][py] = clBlue;
	else Canvas->Pixels[px][py] = clWhite;

  }
 }	
  
}
//---------------------------------------------------------------------------


シェルピンスキのガスケット、シェルピンスキのカーペット、コッホ曲線を描いたら、次は「真打ち」のマンデルブロ集合となるのが相場だが、あいにく私は、ジュリア集合は描いたことがあるものの、マンデルブロ集合を描いたことがない。

それで、マンデルブロ集合を描く前にぽっくり死んだとしてもあまり後悔しないと思うが、とりあえず生きているうちに描こうと努力することにした。

さて、マンデルブロ集合やジュリア集合は複素力学系と呼ばれていて、複素変数をzとすると、ある複素関数fを以って、z ← f(z)のように繰り返し代入していく。特にマンデルブロ集合やジュリア集合の場合、f(z) = z2 + cのような関数を使う。cは複素定数である。

そこで、z = x + yi, c = a + biのように書き表すと、z ← f(z)は
x ← x2 - y2 + a
y ← 2xy + b
と変形される。

普通、力学系z ← f(z)というとき、zの軌跡を追ったりするが、この場合、f(z)のパラメータをいろいろ変えて、z ← f(z)がある有限の絶対値内に収まるかどうかを調べるのが、マンデルブロ集合やジュリア集合なのであった。特にそのとき、z ← f(z)の初期値z0 = 0と置き、異なるcごとに有限性をみていくのがマンデルブロ集合、cをあらかじめ決めておき、初期値z0をいろいろ変えてみるのがジュリア集合である。すなわち、当然にジュリア集合の方が多様である。歴史的にはまず、ジュリア集合があって、その後、その特殊な場合としてマンデルブロ集合が見出されたようである。

それで、ともかくとりあえず描いてみたマンデルブロ集合の画像と、そのプログラムを示す。マンデルブロ集合の画像は以下のとおりである。


ふつう、閾値を超えたループの回数で色分けするらしいのだが、そのあたりのノウハウが私はまったくないので、折角のエキゾチックなマンデルブロ集合が、田舎の案山子的な気の抜けたものになってしまっている。しかし、私の画像デザイン的センスは、プログラム的センスよりはるかに劣るのだから仕方ない。そのうち時間をみて、もう少し見るに堪えるものにしてみる。

それにしても、昔はこのような描画が、下手したら丸一日とかかかったりしたものだが、いまだと1分もかからなかったりする。時代の進歩は、少なくともこの分野では速い。

あ、そうそう、マンデルブロ集合がフラクタルである、というのは、シェルピンスキのガスケットのような相似次元を適用できないので、極めて非自明で、その次元もハウスドルフ次元などを使うしかなくて、計算も難しい。まあ、余裕があったら、そのうち勉強してみる。

あと、下記のコードで、

tx = x*x - y*y + a;
ty = 2.0*x*y + b;
x = tx;
y = ty;
のようにいったん仮変数tx, tyに代入するのは本質的で(じつはtyは余計だが)、こうしないと、次ステップの値であるxが、いきなり次の式に代入されて計算が狂う。そんな初歩的なミスを犯す人はいないと思うが(私だよ!)、一応念のため記しておく。じつは、このミスをすると、マンデルブロ集合の首が括れなくて、猪首になってしまうのだった。

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Main.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
	: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 Close();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
double x,y,tx,ty;
double a;
double b;
int px, py;
int A,B,n;

 for ( A = -250; A < 200; A++ )     {
   for ( B = -200; B < 200; B++ )  {
     x = 0.0; y = 0.0;
	 n = 0;
	 a = (double) A /120.0;
	 b = (double) B /120.0;
     while ( x*x + y*y < 4.0 )   {
	  tx = x*x - y*y + a;
	  ty = 2.0*x*y + b;
	  x = tx;
	  y = ty;
      n++;
	  if ( n > 300 ) break;
	}
	px = A + 500; py = B + 300;
	if ( n > 300 ) Canvas->Pixels[px][py] = clBlack;
    else if ( n == 1 ) Canvas->Pixels[px][py] = clGreen;
    else if ( n == 2 ) Canvas->Pixels[px][py] = clYellow;
    else if ( n == 3 ) Canvas->Pixels[px][py] = clBlue;
	else Canvas->Pixels[px][py] = clRed;
  }
 }	
  
}
//---------------------------------------------------------------------------

コッホ曲線

user-pic
0

コッホ曲線は、フラクタルの一種として典型的なものであるが、なぜか私はパソコンで描いてみたことはない。それで、この際、シェルピンスキのカーペットに続いて、プログラミングにより描いてみることにした。

それは例えば、次のような曲線である:

コッホ曲線は要するに、直線を三等分して中間の2点を切り取り、その2点を底辺とする正三角形を描き、正三角形の頂点と中間の2点を線で結ぶ。その処理を、結果の4つの線分に再帰的に適用する、というもので、その描画プログラムは20分くらい考えて、なんとか逆三角関数を使うことで読みきった。それは以下のとおりである。

ところが、読み切ったと思ったら、若干紛れがあった。すなわち、正三角形の頂点が意図するのと逆方向になってしまい、いわゆる「あっち向いてホイ」みたいな状態になってしまうのである。ここから30分くらい長考して、やっと解決策が分かった。下記の赤字のところである。いやあなかなか楽勝ということはないものである。

//---------------------------------------------------------------------------

#include <vcl.h>
#include <math.h>
#pragma hdrstop

#include "Main.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
const double pi = 3.14159;
void fracta(double x1, double y1, double x2, double y2, int level);
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
	: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
   Close();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 double x1, y1, x2, y2;
 Canvas->Pen->Color = clBlack;
 x1 = 30.0;  y1 = 400.0;
 x2 = 800.0; y2 = 400.0;
 fracta(x1,y1,x2,y2,0);
}
//---------------------------------------------------------------------------

void fracta(double x1, double y1, double x2, double y2, int level)  {
 double X1,Y1, X2,Y2, X3,Y3, X4,Y4, X5,Y5, TH, S;
int Level = level;
 Level++;
if ( Level > 6 )      {
  Form1->Canvas->MoveTo(x1,y1);
  Form1->Canvas->LineTo(x2,y2);
  return;
}

 X1 = x1; Y1 = y1; X2 = x2; Y2 = y2;
 X3 = (X2 - X1)/3.0 + X1;  Y3 = (Y2 - Y1)/3.0 + Y1;
 X4 = 2.0 * (X2 - X1)/3.0 + X1;  Y4 = 2.0 * (Y2 - Y1)/3.0 + Y1;
 S = sqrt((X4-X3)*(X4-X3) + (Y4-Y3)*(Y4-Y3));
 TH = acos((X4 - X3)/S);
 if ( Y4 - Y3 < 0 ) TH = - TH;
 X5 = X3 + S * cos(TH - pi/3.0);
 Y5 = Y3 + S * sin(TH - pi/3.0);
 fracta(X1, Y1, X3, Y3, Level);
 fracta(X3, Y3, X5, Y5, Level);
 fracta(X5, Y5, X4, Y4, Level);
 fracta(X4, Y4, X2, Y2, Level);
}

シェルピンスキのガスケットに続き、その姉妹版である、シェルピンスキのカーペットを作成した。以下のとおりである。エキゾチック感はシェルピンスキのガスケットの方があるが、これはこれで安定感があってよく、本当にカーペットのデザインに使えそうである。

なお、シェルピンスキのガスケットのフラクタル次元(位相次元)は、log(3)/log(2) = 1.58496... であり、シェルピンスキのカーペットのフラクタル次元(位相次元)は、log(8)/log(3) = 1.89279...なので、カーペットの方が次元が高い。これは目で見た直観とも一致する。

また、シェルピンスキのガスケットも、シェルピンスキのカーペットも、中抜きして自己同型縮小写像を再帰的に適用しているところに意味があり、中抜きしないと次元は2、すなわちベタっと塗りつぶすことになってしまう。

以下に、C++ビルダのソースコードを示す。
//---------------------------------------------------------------------------
#include <vcl.h>
#include <math.h>
#pragma hdrstop

#include "Main.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
void Fracta(double p1x, double p1y, double p2x, double p2y, double p3x, double p3y,
 double p4x, double p4y,int level);
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
 Close();    
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 Canvas->Pen->Color = clBlack;
 double a = 20.0;
 double b = 20.0;
 double d = 400.0;
 // 初期位置のセット

 Fracta(a,b, a + d,b,  a + d, b + d, a, b + d,1); 
}
//---------------------------------------------------------------------------

void Fracta(double p1x, double p1y, double p2x, double p2y, double p3x, double p3y,
 double p4x, double p4y,int level)  {
 double a,b,d;
 int depth = level;
 if ( depth > 8 ) return;
 depth++;	

 d = (p3x - p1x)/3.0; 
 
 Form1->Canvas->MoveTo(p1x,p1y);
 Form1->Canvas->LineTo(p2x,p2y); 
 Form1->Canvas->LineTo(p3x,p3y);
 Form1->Canvas->LineTo(p4x,p4y);
 Form1->Canvas->LineTo(p1x,p1y);
 
 a = p1x;  b = p1y;
 Fracta(a,b,a + d,b,a + d, b + d, a, b + d, depth);
 a = p1x + d;  b = p1y;
 Fracta(a,b,a + d,b,a + d, b + d, a, b + d, depth);
 a = p1x + 2.0 * d;  b = p1y;
 Fracta(a,b,a + d,b,a + d, b + d, a, b + d, depth);
 a = p1x;  b = p1y + d;
 Fracta(a,b,a + d,b,a + d, b + d, a, b + d, depth);
 a = p1x + 2.0 * d;  b = p1y + d;
 Fracta(a,b,a + d,b,a + d, b + d, a, b + d, depth);
 a = p1x;  b = p1y + 2.0 * d;
 Fracta(a,b,a + d,b,a + d, b + d, a, b + d, depth);
 a = p1x + d;  b = p1y + 2.0 * d;
 Fracta(a,b,a + d,b,a + d, b + d, a, b + d, depth);
 a = p1x + 2.0 * d;  b = p1y + 2.0 * d;
 Fracta(a,b,a + d,b,a + d, b + d, a, b + d, depth);
 return;
}

フラクタルを勉強していると、最初の方に、シェルピンスキのガスケットというのが出てくる。それは結構有名であるが、画像で示すと以下のようなものである。シンプルながら、なんとなく神秘的な雰囲気もあるこの描画が私は好きである。

最初にフラクタルのことを知った時、シェルピンスキって誰だろうとさえ思いもしなかったが、志賀浩二著「無限からの光芒 - ポーランド学派の数学者たち」日本評論社を読んだとき、シェルピンスキがポーランド学派の偉い数学者の一人であることを知った。

シェルピンスキは1882年に、ワルシャワの有名な医師の息子として生まれた。二つの世界大戦を含む激しい騒乱を生き延びて1969年に87歳で亡くなるまで、ポーランドの大学で数学の顕著な業績を挙げた。その業績には整数論なども含まれるが、なんといってもシェルピンスキを特徴づけるのは、カントールを嚆矢とする無限論、あるいは連続体仮説の掘り下げである。「無限からの光芒 - ポーランド学派の数学者たち」から、例えば、シェルピンスキが証明した次のような定理を掲げてみる。

連続体仮説は、次の命題と同値である:平面R2は、共通点のない二つの部分集合A、Bの和として表される。すなわち、R2 = A∪B ここで、Aはx軸と平行な任意の直線と高々加算個しか交わらず、Bはy軸と平行な任意の直線と高々加算個しか交わらない。

この定理の意味を考えていたら気が遠くなりそうになってきたので、戻ってシェルピンスキのガスケットの表示にもちいたプログラムのソースコードを示しておくことにする。シェルピンスキのガスケットは、私が三十代の前半だった歳の頃もプログラムを作成したが、πの多倍長計算などと比較しても簡単で、ほとんど苦労した記憶はない。なので今回も楽勝かとプログラミングしようとしたら、なんと、正三角形の頂点を結ぶ直線を引く計算でさえ、ええとどうだったっけ、と迷う有様で、この程度のプログラミングもほとんど半日がかりだった。ともかく、動いたC++ビルダのコードを示す。

あ、もう一つ付け加えると、数学セミナー2017年3月号のフラクタル特集号の、梶野直孝氏の「フラクタル上の解析学」と題する記事によると、シェルピンスキのガスケット上でディリクレ形式やラプラシアンが論じられるのだという。ううん、こちらの方もまた気が遠くなりそうである。

//---------------------------------------------------------------------------
#include <vcl.h>
#include <math.h>
#pragma hdrstop

#include "Main.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
const double pi = 3.14156;
double PosX = 300.0;
double PosY = 300.0;
void Fracta(double p1x, double p1y, double p2x, double p2y, double p3x, double p3y, int level);
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
 Close();    
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 Canvas->Pen->Color = clBlack;
 double P1x, P1y, P2x, P2y, P3x, P3y, Angle;
 double L = 250.0;
 double InitAngle = pi;
 double OffsetAngle = 2.0*pi/3.0;
 Angle = InitAngle;
 P1x = L*sin(Angle);  P1y = L*cos(Angle);
 Angle += OffsetAngle;
 P2x = L*sin(Angle);  P2y = L*cos(Angle);
 Angle += OffsetAngle;
 P3x = L*sin(Angle);  P3y = L*cos(Angle);
 Fracta(P1x, P1y, P2x, P2y, P3x, P3y, 1); 
}
//---------------------------------------------------------------------------

void Fracta(double p1x, double p1y, double p2x, double p2y, double p3x, double p3y, int level)  {
 int depth = level;
 if ( depth > 9 ) return;
 depth++;	

 Form1->Canvas->MoveTo(p1x + PosX,p1y + PosY);
 Form1->Canvas->LineTo(p2x + PosX,p2y + PosY);
 Form1->Canvas->LineTo(p3x + PosX,p3y + PosY);
 Form1->Canvas->LineTo(p1x + PosX,p1y + PosY);

 Fracta(p1x, p1y, (p1x + p2x)/2.0, (p1y + p2y)/2.0,(p1x + p3x)/2.0, (p1y + p3y)/2.0,depth);
 Fracta(p2x, p2y, (p2x + p1x)/2.0, (p2y + p1y)/2.0,(p2x + p3x)/2.0, (p2y + p3y)/2.0,depth);
 Fracta(p3x, p3y, (p3x + p1x)/2.0, (p3y + p1y)/2.0,(p3x + p2x)/2.0, (p3y + p2y)/2.0,depth);
 return;
}

フラクタル

user-pic
0

数学セミナーという雑誌は難解なので、私はバックナンバーとしてしばらく積んでおいて後で眺めることが多いが、2017年3月号は、フラクタルの特集号だった。それで、30代の前半の頃、パソコンでフラクタルの図形を描画したことを思い出した。

私は基本的に特定の本をバイブルのように扱うことはないが、当時、一時的にバイブルのように読んでいた本は、山口昌哉著、「カオスとフラクタル」ブルーバックスだった。私にしては珍しく、ぼろぼろになるまで読んだ。また、シルピンスキーのガスケットや、ジュリア集合などをパソコンで描いた。

そういう時代を思い出しつつ、数学セミナー2017年3月号の、荒井迅氏の「複素力学系のフラクタル」という記事を自分の勉強のために簡単にまとめてみる。

その記事によると、フラクタルFは次のような性質をもつ。

  1. Fには微細構造がある。どんなに拡大しても微細構造があらわれる。
  2. Fはとても不規則で、伝統的な幾何学では記述できない。
  3. Fは何らの意味で自己相似性をもつ。
  4. Fのフラクタル次元は、位相次元よりも大きい。
  5. Fは単純に定義される。

ここで、位相次元とは普通の意味での次元であり、整数値をとる。それは例えば、次元消滅で議論した次元のことである。そしてそこで、集合に次元を定義するには位相を導入するよりないと言ったことが、位相次元と呼ばれることと関係があるのだと思う。

一方、フラクタルの複雑さを測るに適した次元をフラクタル次元と呼ぶが、それにもいろいろな次元がある。そのうち計算が簡単な相似次元は、ある集合が自分自身のm個のコピーからなり、コピーの縮小率をrとするとき、-log m/log rと定義される。

例えば、コッホ曲線は、4個のコピーをもち、その縮小率は1/3なので、相似次元は、-log 4/log (1/3) = log 4/log 3 = 1.261859・・・ > 1となる。逆に3個のコピーをもち、その縮小率が1/4のようなものはありえるのだろうか。それだと相似次元は1より小さくなるが、すると、もしかしていくつかの点に収束してしまうような気がする。少なくともそれはフラクタルとは呼ばれない。

これ以外にもハウスドルフ次元というのがあるらしいが、定義が難しいので、ここでは述べない。

ところで、fを複素数CからCへの写像として、f(z) = z2 + cで、cは複素数の定数というものを考える。

そのとき、K := {z∈C|集合{fn(z)|n≧0}が有界}を充填ジュリア集合と呼び、その境界をジュリア集合と呼ぶ。すなわち、ジュリア集合はzを動かす。

一方、K := {c∈C|集合{fn(0)|n≧0}が有界}をマンデルブロ集合と呼ぶ。すなわち、マンデルブロ集合はcを動かす。

私はまだ、8ビット・パソコンのBASICの時代に、ジュリア集合を描いた記憶がある。ジュリア集合は、選ぶcの値によって、がらっと異なる図形になる。ジュリア集合もマンデルブロ集合も、ある閾値を超える繰り返し回数nに従って、描画する点の色を変えることで玄妙な図形になる。

ジュリア集合やマンデルブロ集合のフラクタル次元がどうなるか、その計算はコッホ曲線などの場合よりもはるかに難しい。このとき、ハウスドルフ次元を使う。

なお、計算可能なc∈Cで、ジュリア集合が計算不可能になるものが存在する、というブレイハーマン-ヤンポルスキーの定理というのがあるらしい。さっぱり意味が理解できないが、ともかく深遠さが窺われる。

その他に、3次元フラクタルとか、経済物理学におけるマンデルブロの研究など様々な記事があるので、じっくり読んでみる。

また、コンピュータによるジュリア集合やマンデルブロ集合の描画もそのうち試してみる。その理論はともかく描画だけなら、素数判定プログラムより楽である。

このアーカイブについて

このページには、過去に書かれたブログ記事のうち数学カテゴリに属しているものが含まれています。

前のカテゴリは教育です。

次のカテゴリは日常です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。