【Ruby】マンデルブロ集合をNArrayで描出してみた

小ネタですよ。

![mandel.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/d9faea10-2b66-730a-a247-6f099c9a2a14.png)


旧NArrayの公式ページにサンプルがあるので、Numo::NArrayで書き直してみました。ほとんどそのままです。
https://masa16.github.io/narray/demo/mandel.en.html
Rubyだし実行に時間がかかるんじゃないの？　いいえ、NArrayを使っているので計算時間は一瞬です。
（むしろ画像のサイズが大きくなるとgdkpixbufで画像化するのがもたつくかも）

```bash
gem install numo-narray
gem install gtk3
```

```ruby
require 'numo/narray'
require 'gdk_pixbuf2'
include Numo

def mandel(w, h, w2, h2, zoom)

  z = (SComplex.new(1, w).seq / w - w2) * zoom +
      (SComplex.new(h, 1).seq / h - h2) * zoom * Complex(0, 1)

  c = z.dup
  a = UInt8.zeros(w, h)
  idx = Int32.new(w, h).seq

  (1..100).each do |i|
    z = z**2 + c
    idx_t = (z.abs > 2).where
    idx_f = (z.abs <= 2).where
    a[idx[idx_t]] = i
    break if idx_f.empty?

    idx = idx[idx_f]
    z = z[idx_f]
    c = c[idx_f]
  end
  a
end

data = mandel(600, 600, 0.8, 0.5, 2)

# Grayscale to RGB
data2 = UInt8.zeros(600, 600, 3)
data2[true, true, 1] = (SFloat.cast(data) / 100 * 255)

pixbuf = GdkPixbuf::Pixbuf.new(data: data2.to_string, width: 600, height: 600)

pixbuf.save('mandel.png')
```

![mandel.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/3024491a-a28f-5bb2-c6bb-90b939310ce0.png)

冒頭の画像のようにカラフルにしたい？　カラフルにしたいときはこんな感じです。

```ruby
# frozen_string_literal: true

require 'numo/narray'
require 'gdk_pixbuf2'
include Numo

def mandel(w, h, w2, h2, zoom)
  z = (SComplex.new(1, w).seq / w - w2) * zoom +
      (SComplex.new(h, 1).seq / h - h2) * zoom * Complex(0, 1)

  c = z.dup
  a = UInt8.zeros(w, h)
  idx = Int32.new(w, h).seq

  (1..100).each do |i|
    z = z**2 + c
    idx_t = (z.abs > 2).where
    idx_f = (z.abs <= 2).where
    a[idx[idx_t]] = i
    break if idx_f.empty?

    idx = idx[idx_f]
    z = z[idx_f]
    c = c[idx_f]
  end
  a
end

# Cumoの実行速度対策
def uInt8_dstack(ar)
  x = UInt8.zeros(*ar[0].shape, 3)
  x[true, true, 0] = ar[0]
  x[true, true, 1] = ar[1]
  x[true, true, 2] = ar[2]
  x
end

# HSV を RGB に変換
def hsv2rgb(h)
  i = UInt8.cast(h * 6)
  f = (h * 6.0) - i
  p = UInt8.zeros *h.shape
  v = UInt8.new(*h.shape).fill 255
  q = (1.0 - f) * 256
  t = f * 256
  rgb = UInt8.zeros *h.shape, 3
  t = UInt8.cast(t)
  i = uInt8_dstack([i, i, i])
  rgb[i.eq 0] = uInt8_dstack([v, t, p])[i.eq 0]
  rgb[i.eq 1] = uInt8_dstack([q, v, p])[i.eq 1]
  rgb[i.eq 2] = uInt8_dstack([p, v, t])[i.eq 2]
  rgb[i.eq 3] = uInt8_dstack([p, q, v])[i.eq 3]
  rgb[i.eq 4] = uInt8_dstack([t, p, v])[i.eq 4]
  rgb[i.eq 5] = uInt8_dstack([v, p, q])[i.eq 5]
  rgb
end

data = mandel(600, 600, 0.8, 0.5, 2)

# Grayscale to RGB
data2 = hsv2rgb(SFloat.cast(data) / 100)

pixbuf = GdkPixbuf::Pixbuf.new(data: data2.to_string, width: 600, height: 600)

pixbuf.save('mandel.png')
```

あとは、(実際に試したわけではないですが)Cumoを導入することでGPUで動かすこともできますし、

GdkPixbufで画像データを扱っているので、Gtk3で簡単にデスクトップアプリ化することも可能ですね。

この記事は以上です。

## 関連資料
* [Rubyで画像を行列にしたり、行列を画像にしたりする](https://qiita.com/kojix2/items/18e02b7b13d94b62beed)
* [NArrayの簡単なつかい方](https://qiita.com/kojix2/items/0bc5efda0f65e58693a0)
* [Google colab で RubyからGPU を使って行列計算する](https://qiita.com/kojix2/items/5cf18978c930df72d162)
* [【Ruby】10分でGladeを使って作るRuby/GTK3 GUIアプリ](https://qiita.com/kojix2/items/d9da8e08757dcc6b1a7d)