どうも、Fractal Forumsを眺めるのが日課の108Hassiumです。
Fractal Forumsはその名の通りフラクタル関連の話題が集まるフォーラムで、様々なフラクタル図形の画像や描画方法などが投稿されています。
一年ほど前、こんなポストを発見しました。
https://fractalforums.org/share-a-fractal/22/some-old-stuff-maybe-fractal-nostalgia/4213/msg31149#msg31149
このポストでは、「普通のジュリア集合と分解型複素数のジュリア集合を繋いだようなジュリア集合を生成する関数」として(2x−x3−2y2+a,2y−3x2y+b)という関数を紹介しています。
☝(2x-x^3-2y^2+0.2,2y-3x^2y)のジュリア集合※他にもいろいろ紹介されているのですが、今回は(2x−x3−2y2+a,2y−3x2y+b)に話題を絞ります。
一見ただの多項式の組み合わせにしか見えない関数がこのような性質を持つのは、当時の私にとってかなり衝撃的でした。
というわけで、(2x−x3−2y2+a,2y−3x2y+b)という関数がこのような性質を持つ仕組みや、似たような性質を持つ関数の作り方を探る過程の試行錯誤の様子を紹介したいと思います。
Step0:観察
試行錯誤を始める前に、大本の(2x−x3−2y2+a,2y−3x2y+b)という関数の性質を調べてみます。
☝(2x-x^3-2y^2+0.03,2y-3x^2y+0.14)のジュリア集合
☝(2x-x^3-2y^2-0.08,2y-3x^2y+0.1)のジュリア集合
☝(2x-x^3-2y^2+0.16,2y-3x^2y+0.27)のジュリア集合
☝(2x-x^3-2y^2+0.4,2y-3x^2y)のジュリア集合
☝(2x-x^3-2y^2+0.5,2y-3x^2y)のジュリア集合
☝(2x-x^3-2y^2,2y-3x^2y+0.02)のジュリア集合
☝(2x-x^3-2y^2-0.35,2y-3x^2y)のジュリア集合(x=-0.8付近の20倍拡大)ジュリア集合です。
☝(2x-x^3-2y^2+a,2y-3x^2y+b)のマンデルブロ集合((x_0,y_0)=(-0.8,0))
☝(2x-x^3-2y^2+a,2y-3x^2y+b)のマンデルブロ集合((x_0,y_0)=(0.8,0))
☝(2x-x^3-2y^2+a,2y-3x^2y+b)のマンデルブロ集合((x_0,y_0)=(±0.8,0))(±0.8,0)を初期値にすると、それぞれ分解型複素数のものと普通の複素数のz2+cのマンデルブロ集合に似た形のマンデルブロ集合になるようです。
Step1:場合分け
(2x−x3−2y2+a,2y−3x2y+b)のジュリア集合を観察してみると、「右半分に分解型複素数のジュリア集合があり、左に複素数のジュリア集合がある」という感じの見た目になっているものが多いです。
というわけで、こんなコードを書いてみました。
void setup(){
size(2000,2000);
background(0);
noStroke();
double x,y,px,py,cx,cy;
boolean o;
for(int a=0;a<2000;a++){
for(int b=0;b<2000;b++){
cx=(double)a/500.0-2.0;
cy=(double)b/500.0-2.0;
x=-1.0;
y=0;
o=true;
for(int c=0;c<500&&o;c++){
px=x;
py=y;
if(x<0){
x=px*px-py*py+2.0*px+cx;
y=2.0*px*py+2.0*py+cy;
}else{
x=px*px+py*py-2.0*px+cx;
y=2.0*px*py-2.0*py+cy;
}
if(x*x+y*y>100){
fill(cr(c*7),cr(c*8),cr(c*9));
rect(a,b,1,1);
o=false;
}
}
}
}
}
float cr(float c){
return (c%256)*(256-(c%256))/65;
}
※言語はProcessing(参考)
このプログラムでは、以下のような関数のマンデルブロ集合を(x0,y0)=(−1,0)で描画します。
(F(x,y),G(x,y))={(x2−y2+2x+a,2xy+2y+b)(x2+y2−2x+a,2xy−2y+b)if x<0if 0≤x
xが負のときの式が普通の複素数のz2+2z+cの式で、もう片方は分解型複素数のz2−2z+cの式です。
以下、このような「x<0のときが複素関数、0≤xが分解型複素関数」というような関数を(z2+2z+c∣z2−2z+c)というように表記することにします。
さて、(z2+2z+c∣z2−2z+c)のマンデルブロ集合は以下のようになりました。
☝(z^2+2z+c|z^2-2z+c)のマンデルブロ集合(z_0=-1)
☝(z^2+2z+c|z^2-2z+c)のマンデルブロ集合(z_0=1)真ん中に複素数、右端に分解型複素数のマンデルブロ集合っぽい形が見えますが、分解型複素数の方は何故か中途半端に切れたような形になり、さらに±1(z2±2z+cの臨界点)のどちらを初期値にしても同じ見た目のマンデルブロ集合になってしまっています。
ジュリア集合はこんな感じでした。
☝(z^2+2z+0.3+0.5i|z^2-2z+0.3+0.5j)のジュリア集合
☝(z^2+2z-1.01+0.25i|z^2-2z-1.01+0.25j)のジュリア集合
☝(z^2+2z+0.3|z^2-2z+0.3)のジュリア集合右半分の領域は一応分解型複素数っぽさはあるものの、(2x−x3−2y2+a,2y−3x2y+b)とは全然違う感じの見た目になってしまっています。
ところで、さっきのコードでは収束領域の中を彩色する機能は削ってありますが、導入してみたところこんな画像も得られました。
☝(z^2+2z+c|z^2-2z+c)のマンデルブロ集合
☝(z^2+2z+0.7|z^2-2z+0.7)のジュリア集合色々と面白い部分があり、特に(z2+2z+0.7∣z2−2z+0.7)のジュリア集合はとても美しくなって驚いたのですが、やはり「(2x−x3−2y2+a,2y−3x2y+b)みたいな関数を作る」という目的にはそぐわない結果です。
というわけで、似たような関数をいくつか作ってみたところ(z2+2z+c∣−z2+2z+c)がかなり理想に近い結果になりました。
☝(z^2+2z+c|-z^2+2z+c)のマンデルブロ集合(z_0=-1)
☝(z^2+2z+c|-z^2+2z+c)のマンデルブロ集合(z_0=1)
☝(z^2+2z+0.36+0.1i|-z^2+2z+0.36+0.1j)のジュリア集合また、(z2+rz+c∣−z2+rz+c)のrの値を動かすと以下のようになりました。
☝(z^2+rz+c|-z^2+rz+c)のマンデルブロ集合(z_0=±r/2,r=1~4)二つのマンデルブロ集合の重なり方を見ると、r=2よりr=3の方がより(2x−x3−2y2+a,2y−3x2y+b)に近いように見えます。
☝(z^2+3z+c|-z^2+3z+c)のマンデルブロ集合(z_0=±1.5)というわけでジュリア集合も描画してみました。
☝(z^2+3z+0.6|-z^2+3z+0.6)のジュリア集合
☝(z^2+3z+1.1|-z^2+3z+1.1)のジュリア集合
☝(z^2+3z+1.4|-z^2+3z+1.4)のジュリア集合
☝(z^2+3z+0.08+0.33i|-z^2+3z+0.08+0.33j)のジュリア集合
☝(z^2+3z+0.4+0.62i|-z^2+3z+0.4+0.62j)のジュリア集合やはり理想的です。
Step2:絶対値
(z2+3z+c∣−z2+3z+c)のような場合分けのある関数は、絶対値を使って場合分け無しで綺麗に表せることがあります。
実際、(−∣x∣x−y2+3x+a,−2∣x∣y+3y+b)という関数をxの符号ごとに分けて考えると
(−∣x∣x−y2+3x+a,−2∣x∣y+3y+b)={(x2−y2+3x+a,2xy+3y+b)(−x2−y2+3x+a,−2xy+3y+b)if x<0if 0≤x
・・・というようになり、(z2+3z+c∣−z2+3z+c)のxy表現と一致することが確かめられます。
一方、(z2+2z+c∣z2−2z+c)を場合分け無しで表すと以下のようになります。
(x2+sign(x)y2−sign(x)2x+a,2xy−sign(x)2y+b)
sign(x)はxの符号を表し、絶対値関数を使うとx∣x∣と表せます。
☝y=|x|/xのグラフ※x∣x∣はx=0では未定義になるのですが、気にしないことにします。
ところで、(z2+2z+c∣z2−2z+c)マンデルブロ集合やジュリア集合には、不自然に切り取られたような箇所が存在していました。
☝(z^2+2z+c|z^2-2z+c)のマンデルブロ集合(再掲)
☝(z^2+2z+0.3+0.5i|z^2-2z+0.3+0.5j)のジュリア集合(再掲)これは不連続な関数から生成されるフラクタル図形によく見られる特徴なのですが、場合分けを使わない表記にしてみると符号関数が出てくるため(z2+2z+c∣z2−2z+c)が不連続であることがわかりやすくなります。
Step3:3次関数
(−∣x∣x−y2+3x+a,−2∣x∣y+3y+b)の−x∣x∣+3xの部分のグラフは、こんな形をしています。
y=-|x|x+3xのグラフ2つの放物線を点対象につないだ形なのですが、3次関数のグラフによく似ています。
このことに気付いたとき、「−∣x∣x+3xと−2∣x∣yを多項式関数で上手く近似できれば、絶対値関数を使わずに『複素数と分解型複素数のハイブリット関数』を作れるのではないか」という事を思いつきました。
というわけで、試行錯誤の結果がこちらです。
☝(-x^3/3+2.25x-y^2+a,-2.25x^2y+3y+b)のマンデルブロ集合((x_0,y_0)=(-1.5,0))
☝(-x^3/3+2.25x-y^2+a,-2.25x^2y+3y+b)のマンデルブロ集合((x_0,y_0)=(1.5,0))−∣x∣x+3xが3−x3+2.25xに、−2∣x∣yが−2.25x2yに置き換わっています。
係数の決定には手探りの微調整が必要で面倒臭いし、結果として得られた係数は中途半端な値で美しくないし、なにより肝心のマンデルブロ集合の見た目が崩れすぎているのが気に入りません。
どうしたものかと考えていたところ、全く違うアプローチを思いつきました。
まず、r(−3z3+z)+cのマンデルブロ集合は以下のようになります。
☝r(-z^3/3+z)+cのマンデルブロ集合(z_0=±1,r=1~4)r(−3z3+z)+cの臨界点は±1で、どちらに対応するマンデルブロ集合もz2+cのものに似た形をしていて、rの値によって大きさと位置が変化します。
もし2つのマンデルブロ集合のうち一方が分解型複素数のものに変われば、(z2+rz+c∣z2−rz+c)とよく似た関数になります。
というわけで、(r(−3z3+z)+c∣r(−3z3+z)+c)のマンデルブロ集合を描画してみました。
☝(r(-z^3/3+z)+c|r(-z^3/3+z)+c)のマンデルブロ集合(z_0=±1,r=1~4)予想通り(?)に、z0=1の方のマンデルブロ集合が四角い形になりました。
ということは、(r(−3z3+z)+c∣r(−3z3+z)+c)を多項式関数で近似できれば「絶対値関数を使わずに『複素数と分解型複素数のハイブリット関数』を作る」という目的を達成できそうです。
ちなみにマンデルブロ集合の重なり方を見ると、r=2とすると(2x−x3−2y2+a,2y−3x2y+b)や(z2+3z+c∣z2−3z+c)に似た関数になりそうです。
さて、(2(−3z3+z)+c∣2(−3z3+z)+c)を場合分け無しで書き直すと以下のようになります。
(2(−3x3−∣x∣y2+x)+a,2(−x2y−3sign(x)y3+y)+b)
絶対値関数と符号関数がそれぞれ1回ずつ使われているので、両方とも多項式関数による近似ができればよさそうです。
結論から言うと、∣x∣を1に、sign(x)は0に置き換えると以下のようになります。
☝(2(-x^3/3-y^2+x)+a,2(-x^2y+y)+b)のマンデルブロ集合((x_0,y_0)=(-1,0))
☝(2(-x^3/3-y^2+x)+a,2(-x^2y+y)+b)のマンデルブロ集合((x_0,y_0)=(1,0))∣x∣≈1、sign(x)≈0というめちゃくちゃ雑な近似で上手くいく理由はさっぱりわからないのですが、とりあえず上手くいったのでヨシとします。
最後に、(2x−x3−2y2+a,2y−3x2y+b)の正体(?)について個人的な見解を述べます。
まず、元となった関数は2z−z3+cで、「(±0.8,0)を初期値にするといい感じのマンデルブロ集合になる」の±0.8という値は2z−z3+cの臨界点である±36(≒±0.816)という値に関係していると考えられます。
そして、私が2(−3z3+z)+cから(2(−3x3−y2+x)+a,2(−x2y+y)+b)に辿り着いたのと同じ行程を踏むと、(2x−x3−3y2+a,2y−3x2y+b)という関数になります。
(2x−x3−2y2+a,2y−3x2y+b)と見比べると1か所だけ係数が異なり、この違いは∣x∣を1ではなく32で近似したことに相当します。
もちろんこれは「あのポストの投稿者がこのような思考過程を経て(2x−x3−2y2+a,2y−3x2y+b)という関数を発見した」という意味ではないのですが、私としては(2x−x3−2y2+a,2y−3x2y+b)があのような性質を持つ背景を少し見ることができたような気がするので満足です。
おまけ
☝(-z^2-2z+c|z^2-2z+c)のマンデルブロ集合(z_0=-1)
☝(-z^2-2z+c|z^2-2z+c)のマンデルブロ集合(z_0=1)考え方的に(z2−2z+c∣−z2−2z+c)とほとんど変わらないのに何故か上手くいかなかったケースです。
左側には普通の複素数のマンデルブロ集合があって右側には分解型複素数のものがある、という性質を持たせることはできているものの、両者の間に謎の収束領域の塊ができてしまっています。
☝(2x-x^3/6-y^2/2+a,2y-x^2y/2+b)のマンデルブロ集合((x_0,y_0)=(-2,0))
☝(2x-x^3/6-y^2/2+a,2y-x^2y/2+b)のマンデルブロ集合((x_0,y_0)=(2,0))
☝(2x-x^3/6-y^2/2+0.21,2y-x^2y/2)のジュリア集合2z−6z3+cから作った、収束領域のサイズが大きいハイブリッド関数です。
☝(-1.2(x^4+6xy^2+y^4+2(x^3+3y^2)-2x)+a,-1.2(4x^3y+4y^3+2(3x^2y)-2y)+b)のマンデルブロ集合((x_0,y_0)=(-1,0))
☝(-1.2(x^4+6xy^2+y^4+2(x^3+3y^2)-2x)+a,-1.2(4x^3y+4y^3+2(3x^2y)-2y)+b)のマンデルブロ集合((x_0,y_0)=(0.5,0))
☝(-1.2(x^4+6xy^2+y^4+2(x^3+3y^2)-2x)-0.03,-1.2(4x^3y+4y^3+2(3x^2y)-2y))のジュリア集合
☝(-1.2(x^4+6xy^2+y^4+2(x^3+3y^2)-2x)-0.12,-1.2(4x^3y+4y^3+2(3x^2y)-2y)+0.1)のジュリア集合
☝(-1.2(x^4+6xy^2+y^4+2(x^3+3y^2)-2x)+0.5,-1.2(4x^3y+4y^3+2(3x^2y)-2y))のジュリア集合−1.2((z+1)4−2(z+1)3)+cをもとにした関数です。
「複素数のz3+cと分解型複素数のz2+cのハイブリッド」みたいなものができるんじゃないかと期待したのですが、調整がうまくいきませんでした。