洋食の日記

「た・である」調ではなく「です・ます」調で書きはじめれば良かったなと後悔してる人のブログです

Ruby拡張でNumo::NArrayのデータをポインタで取得する

はじめに

Ruby拡張で、Numo::NArrayのデータをC言語の配列のように扱えないかな〜と思っていたら、na_get_pointer_for_read(for_writeもある)という関数が用意されていて簡単にできた。

準備

もろもろ自動で用意されて便利なので、Ruby拡張を含むgemを作る形で進める。bundle install前に、gemspecファイル中のTODOを削除したり、URLの部分はコメントアウトしたりする必要がある。

$ bundle gem hoge --ext
Creating gem 'hoge'...
MIT License enabled in config
Code of conduct enabled in config
      create  hoge/Gemfile
      create  hoge/lib/hoge.rb
... (省略)

$ cd hoge
$ vim hoge.gemspec
... 「TODO:」を削除する
... spec.homepageなどURLを書く部分をコメントアウトする
... Numo::NArrayを依存関係に追加する
spec.add_runtime_dependency 'numo-narray'
... (省略)

$ bundle install
...

extconfにNumo::NArray関連の記述を追加する。

$  vim ext/hoge/extconf.rb
require 'mkmf'
require 'numo/narray'

$LOAD_PATH.each do |lp|
  if File.exist?(File.join(lp, 'numo/numo/narray.h'))
    $INCFLAGS = "-I#{lp}/numo #{$INCFLAGS}"
    break
  end
end

create_makefile('hoge/hoge')

ライブラリでもNumo::NArrayを読み込むようにする。

$ vim lib/hoge.rb
require 'numo/narray'
require 'hoge/version'
require 'hoge/hoge'

module Hoge
  class Error < StandardError; end
  # Your code goes here...
end

拡張の作成

関連ファイルのinclude

まずは、Numo::NArray関連のヘッダーファイルをincludeする。

$ vim ext/hoge/hoge.h
#ifndef HOGE_H
#define HOGE_H 1

#include "ruby.h"
#include "numo/narray.h"
#include "numo/template.h"

#endif /* HOGE_H */

Numo::NArrayのデータをポインタで取得する

Numo::DFloatの中身を表示するメソッドをもつモジュールを作る。

$ vim ext/hoge/hoge.c
#include "hoge.h"

VALUE rb_mHoge;

static VALUE
print_mat(VALUE self, VALUE vnary)
{
  narray_t *nary;
  size_t i, j;
  size_t n_rows, n_cols;
  double* data;

  /* VALUEなNArrayからnarray_t構造体を得る */
  GetNArray(vnary, nary);

  /* ホントはNumo::DFloatか確認するコードがあると良い
   * if (CLASS_OF(vnary) != numo_cDFloat) { ... とか
   */

  /* ホントは次元数が2か(行列かどうか)のコードがあると良い
   * if (NA_NDIM(na) != 2) { ... とか
   */

  /* NArrayの行数・列数を得る */
  n_rows = NA_SHAPE(nary)[0];
  n_cols = NA_SHAPE(nary)[1];
  printf("(%zd, %zd)\n", n_rows, n_cols);

  /* Numo::DFloatから配列の先頭ポインタを得る */
  data = (double*)na_get_pointer_for_read(vnary);

  /* 配列の中身を表示する */
  for (i = 0; i < n_rows; i++) {
    printf("[ ");
    for (j = 0; j < n_cols; j++) {
      printf("%.4f ", data[i * n_cols + j]);
    }
    printf("]\n");
  }

  return Qnil;
}

void
Init_hoge(void)
{
  rb_mHoge = rb_define_module("Hoge");
  rb_define_module_function(rb_mHoge, "print_mat", print_mat, 1);
}

動作確認

コンパイルして動作を確認する。渡したNumo::DFloatの中身を取得して表示することができた。

$ rake compile
...

$ bin/console
irb(main):001:0> a = Numo::DFloat.new(3,2).rand
=> Numo::DFloat#shape=[3,2]
[[0.0617545, 0.373067],
 [0.794815, 0.201042],
 [0.116041, 0.344032]]
irb(main):002:0> Hoge.print_mat(a)
(3, 2)
[ 0.0618 0.3731 ]
[ 0.7948 0.2010 ]
[ 0.1160 0.3440 ]
=> nil

おわりに

Ruby拡張を作っていると、外部ライブラリとアレコレするとか、完全にC言語の世界に行きたい場合があって、Arrayがポインターで取れたら良いのにな〜と思ってたら簡単にできた。