はじめに
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がポインターで取れたら良いのにな〜と思ってたら簡単にできた。