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

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

###目次
[Cを使ってRubyのメソッドを書く(その1)](https://qiita.com/ki073/items/2b04b27bc421d028c42b)
Cを使ってRubyのメソッドを書く(その2) Numo::NArray <- ここ

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

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

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

```c:test2.h
double sum_d(int n, double x[]);
void twicefold_dv(int n, double x[], double y[]);
```

```c: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[]でデータを返しています。かなり乱暴な方法ですが、ともかく配列データを受け渡しできるというレベルです。

```ruby: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
```

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

```ruby: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に情報がありますので、必要があれば加えてください。

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

```ruby: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