(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[]が配列のポインタです。
double sum_d(int n, double x[]);
void twicefold_dv(int n, double x[], double y[]);
# 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;
}
}
%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[]でデータを返しています。かなり乱暴な方法ですが、ともかく配列データを受け渡しできるというレベルです。
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フォルダに位置ですので間違えないように。
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を作成して、それを返すように改良しています。
%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に情報がありますので、必要があれば加えてください。
他のファイルの変更はありませんのでそのまま使えます。コマンドも全く同じです。
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