LoginSignup
2
1

More than 3 years have passed since last update.

Cを使ってRubyのメソッドを書く(その2) Numo::NArray

Last updated at Posted at 2020-08-28

(2020.9.10 追記)
改良版のtest.iをさらに改良しました

目次

Cを使ってRubyのメソッドを書く(その1)
Cを使ってRubyのメソッドを書く(その2) Numo::NArray <- ここ

1. 配列を渡す場合には

配列を渡す方法としてはいくつかあります。マニュアルにはcarray.iの使い方が書いてありますが、かなり使いにくいです。

2. Numo::NArrayを使う方法

Numo::NArrayは高速処理ができることもあり、使っている方も多いと思います。それ経由でCで書いた関数に渡す方法を紹介します。

配列を受け取り合計を計算する関数と2倍の値を計算する関数です。
int nが配列の要素数、double x[]が配列のポインタです。

test2.h
double sum_d(int n, double x[]);
void twicefold_dv(int n, double x[], double y[]);
test2.c
# include "test2.h"

double sum_d(int n, double x[]){
  double sum=0;
  for(int i=0; i<n; i++){
    sum +=x[i];
  }
  return(sum);
}


void twicefold_dv(int n, double x[], double y[]){
  for(int i=0; i<n; i++){
    y[i]=x[i]*2.0;
  }
}
test.i
%module testF
%{
#include "numo/narray.h"
#include "test2.h"
%}

%typemap(in) double [] {
  narray_t *nary;
  GetNArray($input, nary);
  $1 = ($1_ltype)na_get_pointer_for_read($input);
}
%include test2.h

%typemap(in) double [] {
で、ヘッダファイル(test2.h)中のdouble []にマッチする引数、double ??[]の3箇所に適用されるようになっています。ここでは単にNArray中のデータを指すポインタを関数に渡しているだけで、データ型を全くチェックしていませんし、配列のサイズも int n でしか渡していませんので、間違えると確実にエラーになります。またviewタイプのデータを渡すと正しく読み込むことができません。
Numo::DFloatクラスのデータを作成し、部分的に切り出したりせずviewタイプになっていないと使うことができます。
double y[]でデータを返しています。かなり乱暴な方法ですが、ともかく配列データを受け渡しできるというレベルです。

extconf.rb
equire 'mkmf'
dir_config("numo-narray")
create_makefile("testF")

numo/narray.hの位置を取得するために2行目が追加されています。

swig -ruby test.i
ruby extconf.rb  -- --with-numo-narray-include=/opt/local/lib/ruby2.7/gems/2.7.0/gems/numo-narray-0.9.1.8/lib/numo/
make

2行目でnumo/narray.hの場所を教えていますので、環境に合わせて書き換えてください。narray.hではなくこれが入っているnumoフォルダに位置ですので間違えないように。

test2.rb
require "numo/narray"
require "./testF"

p TestF.sum_d(4, Numo::DFloat.cast([1.0,2.1,3.2,4.3]))
result= Numo::DFloat.zeros(4)
TestF.twicefold_dv(4, Numo::DFloat.cast([1.0,2.1,3.2,4.3]), result)
p result

改良版

NArrayデータ自体は配列の大きさの情報を持っていますので、それを使うように改良しました。またデータ型をNumo::DFloatでなければキャストしています。(キャストされるデータ型はnumo_cDFloatで指定)
またviewタイプの場合にはdupして、正しくデータが読み込まれるようにしています。
ここでは、先のように%includeで読み込むのではなく、test.iの下の方に直接書き込んでいます。

%typemap(in)が2箇所ありますが、最初のものは
double sum_d(int SIZE, double *NARRAY);
にマッチします。
2番目のものは
void twicefold_dv(int SIZE, double *NARRAY_in, double *NARRAY_out);
にマッチするようにしています。
こちらは、さらに戻り値用のNArrayを作成して、それを返すように改良しています。

test.i
%module testF
%{
#include "numo/narray.h"
#include "test2.h"
%}

%typemap(in) (int SIZE, double *NARRAY) {
  narray_t *nary;
  if (rb_obj_class($input)!=numo_cDFloat){
      $input = rb_funcall(numo_cDFloat, rb_intern("cast"), 1, $input);
  }
  GetNArray($input, nary);
  if (NA_TYPE(nary)==NARRAY_VIEW_T){
    $input = rb_funcall($input, rb_intern("dup"), 0);
    GetNArray($input, nary);
  }
  $2 = ($2_ltype)na_get_pointer_for_read($input);
  $1 = NA_SIZE(nary);
}

%typemap(in) (int SIZE, double *NARRAY_in, double *NARRAY_out) (VALUE temp) {
  narray_t *nary1, *nary2;
  VALUE obj2;
  size_t obj2_shape[1];
  if (rb_obj_class($input)!=numo_cDFloat){
      $input = rb_funcall(numo_cDFloat, rb_intern("cast"), 1, $input);
  }
  GetNArray($input, nary1);
  if (NA_TYPE(nary1)==NARRAY_VIEW_T){
    $input = rb_funcall($input, rb_intern("dup"), 0);
    GetNArray($input, nary1);
  }
  $2 = ($2_ltype)na_get_pointer_for_read($input);
  obj2_shape[0] = NA_SIZE(nary1);
  obj2 = rb_funcall(nary_new(numo_cDFloat, 1, obj2_shape), rb_intern("fill"), 1, INT2FIX(0));
  temp=obj2;
  GetNArray(obj2, nary2);
  $3 = ($3_ltype)na_get_pointer_for_read(obj2);
  $1 = NA_SIZE(nary1);
}

%typemap(argout) (int SIZE, double *NARRAY_in, double *NARRAY_out){
  $result=temp1;
}

double sum_d(int SIZE, double *NARRAY);
void twicefold_dv(int SIZE, double *NARRAY_in, double *NARRAY_out);

%typemap(argout)で計算した配列を返すようになっていますが、%typemap(in)の中のtempとは連携が取れていません。ここのtempはtemp1に変数名が変換されているので、それを期待して %typemap(argout)の中ではtemp1としています。かなり危ない書き方ですが、良い方法が見つからなかったので妥協策という事で。それが原因でエラーになるようでしたら書き換えてください。
test_wrap.cの中身を見ると分かります。

配列の次元などをチェックしていませんが、narray.hに情報がありますので、必要があれば加えてください。

他のファイルの変更はありませんのでそのまま使えます。コマンドも全く同じです。

test3.rb
require "numo/narray"
require "./testF"

p TestF.sum_d(Numo::DFloat.cast([1.0,2.1,3.2,4.3]))
p TestF.sum_d(Numo::SFloat.cast([1.0,2.1,3.2,4.3]))
p TestF.sum_d(Numo::SFloat.cast([[1.0,2.1,3.2,4.3],[5,6,7,8]])[1, true])

p TestF.twicefold_dv(Numo::DFloat.cast([1.0,2.1,3.2,4.3]))

macOS 10.13.6 Ruby 2.7.1
SWIG 4.0.2

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1