仮想計算機構

IT業界と無縁な派遣社員のブログ

ジンジャーブレッドマン写像

ジンジャーブレッドマン写像は下記の式で定義される離散力学系です。


x_{n+1}=1-y_n+|x_n|\\
y_{n+1}=x_n

二次元の点(x, y)を50x50=2500個用意して、各点がどのような軌跡を描くのかやってみました。点は座標に応じて色づけしています。具体的にはHLS色空間の色相Hを以下のように計算しています。


h = \arctan(y/x)

結果は下記の通りです。

f:id:riverta1992:20201127212933g:plain

Wikipediaに載っている図と同じようなパターンを再現できました。見た目は単純な写像なのに、なかなか面白い結果です。ジンジャーブレッドマンに似ていることから命名されているようなのですが、自分はデュエルマスターズの「浄化の精霊ウルス」にしかみえませんでした。

以下コードです。

<html>
    <body>
        <script type="text/javascript" src="ginger.js"></script>
    </body>
</html>

const WIDTH = 600;
const HEIGHT = 600;

const canvas = document.createElement('canvas');
canvas.width = WIDTH;
canvas.height = HEIGHT;

const context = canvas.getContext('2d');

document.body.appendChild(canvas);

class Point{
    constructor(id, x, y, color, th){
        this.id = id;
        this.x = x;
        this.y = y;
        this.color = color;
        this.th = th;
    }
    update(){
       let new_x = 1-this.y+Math.abs(this.x);
       let new_y = this.x;

       this.x = new_x;
       this.y = new_y;
    }
    render(context,xmin,xmax,ymin,ymax){
        context.beginPath();
        context.fillStyle = this.color;
        context.fillRect(WIDTH*(xmax-this.x)/(xmax-xmin), HEIGHT*(ymax-this.y)/(ymax-ymin), 1, 1);
        context.fill();
    }
}
context.beginPath();
context.fillStyle = "white"
context.fillRect(0,0,WIDTH, HEIGHT);
context.fill();

N = 50;
mv = 10;
const objects = [];
for(let i=0;i<N;i++){
  for(let j=0;j<N;j++){
    let tx = 2*mv*i/N-mv;
    let ty = 2*mv*j/N-mv;
    let th = 0;
    if(tx > 0){
      th = (180*Math.atan(ty/tx)/Math.PI+360)%360;
    }else if(tx < 0){
      th = (180*Math.atan(ty/tx)/Math.PI+180)%360;
    }
    objects.push(new Point(i+j*N,tx ,ty ,'hsl('+ th +','+70+'%,'+50+'%)', th));
  }
}

for(let i=0;i<N;i++){
  for(let j=0;j<N;j++){
    if(objects[i+j*N].x == 0){
      tmp=0;let count=0;
      if(j>0){
        tmp += objects[i+(j-1)*N].th;
        count += 1;
      }
      if(j+1<N){
        tmp += objects[i+(j+1)*N].th;
        count += 1;
      }
      objects[i+j*N].color = 'hsl('+ tmp/count +','+70+'%,'+50+'%)';
    }
  }
}

let count = 0;
function loop(timestamp) {

  context.clearRect(0, 0, 100, 50);
  objects.forEach((obj) => obj.update());
  objects.forEach((obj) => obj.render(context,-mv,mv,-mv,mv));
  window.requestAnimationFrame((ts) => loop(ts));
  count++;
  context.fillStyle = "black";
  context.font = "20px sanserif";
  context.fillText("n : " + count, 10, 20);

}
window.requestAnimationFrame((ts) => loop(ts));