Ruby Programing for FORTRAN 〜NArrayを使ってみよう(2)〜

内容

前回はFORTRANで書いた1次元配列を扱う関数をNArrayを介してRubyから利用できるようなラッピングを行いました。 このままFORTRANで多次元配列の場合もつづけてつっぱしるというのもアリですが、ここで一度基本のCについて作業してみます。 CはSWIGでサポートされているといっても、NArrayを介そうとおもえば当然特殊な加工が必要なわけですし、なにかと特殊なコーディングが要求されるFORTRANと違って、純粋にNArrayで必要になるコーディングがなにかということも分りやすいでしょうから。 というわけで、今回の題材は2次元配列を扱うCの関数です。

.

まずいつものように題材の関数のコードを示します。

%cat test8.c
#include <stdio.h>
#include <math.h>

char *trans(char *original, int width, int height)
{
  double cosTH, sinTH;
  int x, y, origin_x, origin_y, central_x, central_y;
  char *output;

  cosTH = 0.0;
  sinTH = 1.0;

  if (width % 2 == 0 || height % 2 == 0)
    return(NULL);

  central_x = (width - 1) / 2;
  central_y = (height -1) / 2;
  output = (char *)malloc(width * height * sizeof(char));
  for (y = 0; y < height; y++) {
    for (x = 0; x < width; x++) {
      origin_x = (x - central_x)*cosTH + (y - central_y)*sinTH + central_x;
      origin_y = -(x - central_y)*sinTH + (y - central_y)*cosTH + central_y;
      output[width * y + x]  = original[width * origin_y + origin_x];
    }
  }

  return(output);
}
   

足し算からは卒業しました。さすがに2次元配列で足し算はあんまりですからね..... さて、では何をする関数でしょうか?ぱっと見てもちょっと分らないですよね。 実はこれはoriginalで受けとった2次元の配列(今回はポインタのポインタという形式ではなく連続した形でそれを表現しています)のデータを ドット絵のキャンバスとみなし、そこに描かれた絵を90度回転させたものを再び2次元の配列で返すというものです。

と、これでは多分全然わからないですよね。まあものは試しといいますし(ちょっと違うか:)使用例をみてください。

%cat test8_c.c
#include <stdio.h>

char *trans(char *original, int width);

int main(void)
{
  char *image, *original;
  int width, height, x ,y;

  width = 3;
  height = 3;
  original = (char *)malloc(width * height * sizeof(char));
  original[0] = original[1] = original[2] = 1;
  original[3] = original[4] = original[5] = 0;
  original[6] = original[7] = original[8] = 0;

  if ((image = trans(original, width, height)) == NULL) {
    printf("Error\n");
    return(1);
  }

  printf("original: \n");
  for (y = height - 1; 0 <= y; y--) {
    for (x = 0; x < width; x++) {
      printf("%d", original[width * y + x]);
    }
    printf("\n");
  }

  printf("transformed: \n");
  for (y = width - 1; 0 <= y; y--) {
    for (x = 0; x < height; x++) {
      printf("%d", image[width * y + x]);
    }
    printf("\n");
  }
  free(original);
  return(0);
}
   

実行すると次のような表示になるでしょう。

%gcc -o test8 test8_c.c test8.o
%./test8
original:
000
000
111
transformed:
001
001
001
   

なお、この関数は回転を行って座標が小数になってしまう場合の処理など一切考慮してありません:) またこれは実質キャンバスを回転させることに外ならないですが、その際縦横のサイズが異なると当然上手くいきません:) よってこの関数では回転の中心を実数にするために縦横ともに同じ奇数を与えることを前提しています。 かなり厳しい前提ですねえ....ははは....

さて関数の説明はこれくらいにして、ラッピングに着手するとしましょう。

%cat test8.i
%module test8

%{
#include "narray.h"
VALUE cNArray;
%}

%typemap(ruby,ignore) int LENGTH {
}

%typemap(ruby,in) char *ARRAY(VALUE source) {
struct NARRAY *i_c_na;
source = na_cast_object($source, NA_BYTE);
GetNArray(source, i_c_na);
$target = (char *)NA_PTR(i_c_na, 0);
}

%typemap(ruby,out) char * {
struct NARRAY *o_c_na;
int rank, i;
int *o_c_shape;
rank = 1;
o_c_shape = ALLOC_N(int, rank);
for (i = 0; i < rank; i++) {
o_c_shape[i] = 1;
}
$target = na_make_object(NA_BYTE, rank, o_c_shape, cNArray);
GetNArray($target, o_c_na);
(char *)o_c_na->ptr = $source;
}

char *trans(char *ARRAY, int LENGTH, int LENGTH);
   

配列の長さを渡す際、ポインタにしなくてもよいのでLENGTHの部分は単にignoreのみを指定するだけで終わりにしています。 あとで関数transの引数int width, int heightにわたされることになるarg[0,1]にNArrayから得られる値を直接入れるコードを挿入することになります。 ARRAYに関しては前回のFORTRANの例とほとんど変りません。BYTEの配列に変換してるところが違うぐらいでしょうか。 なお今まででてきませんでしたが、outを指定した場合は関数の返り値を扱うことになります。 その際の張りつけるコードも前回の例と変らないですね。

test8.iについてはこのくらいでだいたい理解していただけたと思います。 ではメインディッシュの生成したコードの修正に移りましょう。 生成コードをいちいち載せるのはちょっと退屈なので修正済みコードのみ載せます。追記(01/09/13)

static VALUE
_wrap_trans(int argc, VALUE *argv, VALUE self) {
    VALUE varg0 ;
    VALUE varg1 ;
    VALUE varg2 ;
    char *arg0 ;
    int arg1 ;
    int arg2 ;
    VALUE source ;
    char *result ;
    VALUE vresult = Qnil;

    {
    }
    {
    }
    rb_scan_args(argc, argv, "10", &varg0);
    {
        struct NARRAY *i_c_na;
        source = na_cast_object(varg0, NA_BYTE);
        GetNArray(source, i_c_na);
//下5行追加
        if (i_c_na->rank != 2)
          rb_raise(rb_eSyntaxError, "rank must be 2!!");
        arg1 = i_c_na->shape[0];
        arg2 = i_c_na->shape[1];
        arg0 = (char *)NA_PTR(i_c_na, 0);
    }
    result = (char *)trans(arg0,arg1,arg2);
    {
        struct NARRAY *o_c_na;
        int rank, i;
        int *o_c_shape;
        rank = 2; //2に書き換え
        o_c_shape = ALLOC_N(int, rank);
//shapeに与える値を入れ換える
        o_c_shape[0] = arg1;
        o_c_shape[1] = arg2;
        vresult = na_make_object(NA_BYTE, rank, o_c_shape, cNArray);
        GetNArray(vresult, o_c_na);
        memcpy((char *)o_c_na->ptr, result, o_c_na->total); //追加
    }
    return vresult;
}
....
void Init_test8(void) {
    int i;

    cNArray = rb_const_get( rb_cObject, rb_intern("NArray") ); //追加
    mTest8 = rb_define_module("Test8");
    _mSWIG = rb_define_module_under(mTest8, "SWIG");
....
}
   

とりあえず、話を簡単にするためにrankは2じゃないと駄目というようにしてしまいましょう。 rankが2の場合、NArrayが内部で保持する配列は今回ラップする関数の配列とぴったり一致、すなわち一見すると単なる1次元配列であるが、それを区切ることで2次元配列を表現するものになります。 たとえばNArray.byte(2, 3)とした場合は

  0  1    2  3    4  5   (実際のインデックス)
-------------------------
| 0     | 1     | 2     |(1次元目のインデックス)
|-------|-------|-------|
||0 |1 |||0 |1 |||0 |1 ||(2次元目のインデックス)
|-------|-------|-------|
-------------------------
   

のような配列を内部で保持することになります。 この際、1次元目が1で2次元目が1の要素にアクセスするとするならば

struct NARRAY *na;
char *ptr;

GetNArray(narray, na);
ptr = (char *)NA_PTR(na, 0);
printf("%d\n", ptr[na->shape[0] * 1 + 1]);
   

のようなコードを書くことになります。これはtest8.cにでてきたアクセスと全く同じですね。これなら変換作業を特にしなくてもすみます。楽だー

なお、何次元の配列になろうともこの構図は変化しません。アクセスする方法もshape部分に少々変化がでる程度です。

NArrayオブジェクトに書き出す処理に関しても返り値が同じ形式になっているのでこれについても苦労することはありません。

ただ、1つだけ注意すべき点はNArrayの内部配列にデータをいれる際、

(char *)o_c_na->ptr = result;
   

のようにはしないようにしましょう。(実は最初やっていたんですが:)

さて、後はmakeして使ってみるだけですね。makeの際の注意点は前回参照ということでここでは省きます。

%make
cc -fPIC -O -pipe  -fPIC -I/usr/local/lib/ruby/1.6/i386-freebsd4.3
 -I/usr/local/include -c -o test8_wrap.o test8_wrap.c
cc -shared -Wl,-soname,test8.so -L/usr/local/lib -o test8.so test8.o
 test8_wrap.o -L. -lruby -lc
%ruby -e 'require "narray"; require "test8"; a = NArray.byte(3, 3); \
a[0, 0] = 1; a[1, 0] = 2; a[2, 0] = 1; b = Test8.trans(a); for i in \
[2, 1, 0] do; for ii in [0, 1, 2] do; print a[ii, i]; end; puts; end'
000
000
121
%ruby -e 'require "narray"; require "test8"; a = NArray.byte(3, 3); \
a[0, 0] = 1; a[1, 0] = 2; a[2, 0] = 1; b = Test8.trans(a); for i in \
[2, 1, 0] do; for ii in [0, 1, 2] do; print b[ii, i]; end; puts; end'
001
002
001
   

one linerとしてはどうかと思うものではありますが、一応意図した通りの動作をしてくれているようです。ああ、よかった。

追記(01/09/13)

以下のようにインターフェースのコードを書き換えると生成コードをあまりいじらなくてすみます。必要なのはInit_test8にcNArrayの値を入れるコードのみ。

%module test8

%{
#include "narray.h"
VALUE cNArray;
typedef char char_2dimensions;
%}

%typemap(ruby,ignore) int FIRST_LENGTH(int *first_length) {
first_length = &$target;
}

%typemap(ruby,ignore) int SECOND_LENGTH(int *second_length) {
second_length = &$target;
}

%typemap(ruby,in) char_2dimensions *ARRAY(VALUE source) {
struct NARRAY *narray;
source = na_cast_object($source, NA_BYTE);
GetNArray(source, narray);
$target = (char *)NA_PTR(narray, 0);
*first_length = narray->shape[0];
*second_length = narray->shape[1];
}

%typemap(ruby,out) char_2dimensions * {
struct NARRAY *narray;
int rank, i;
int *shape;

rank = 2;
shape = ALLOC_N(int, rank);
shape[0] = *first_length;
shape[1] = *second_length;

$target = na_make_object(NA_BYTE, rank, shape, cNArray);
GetNArray($target, narray);
memmove(narray->ptr, $source, narray->total);
}

char_2dimensions *trans(char_2dimensions *ARRAY, int FIRST_LENGTH, int SECOND_LENGTH);
   

生成されるコードは次のようになります。

static VALUE
_wrap_trans(int argc, VALUE *argv, VALUE self) {
    VALUE varg0 ;
    VALUE varg1 ;
    VALUE varg2 ;
    char_2dimensions *arg0 ;
    int arg1 ;
    int arg2 ;
    int *first_length ;
    int *second_length ;
    VALUE source ;
    char_2dimensions *result ;
    VALUE vresult = Qnil;

    {
        first_length = &arg1;
    }
    {
        second_length = &arg2;
    }
    rb_scan_args(argc, argv, "10", &varg0);
    {
        struct NARRAY *narray;
        source = na_cast_object(varg0, NA_BYTE);
        GetNArray(source, narray);
        arg0 = (char *)NA_PTR(narray, 0);
        *first_length = narray->shape[0];
        *second_length = narray->shape[1];
    }
    result = (char_2dimensions *)trans(arg0,arg1,arg2);
    {
        struct NARRAY *narray;
        int rank, i;
        int *shape;

        rank = 2;
        shape = ALLOC_N(int, rank);
        shape[0] = *first_length;
        shape[1] = *second_length;

        vresult = na_make_object(NA_BYTE, rank, shape, cNArray);
        GetNArray(vresult, narray);
        memmove(narray->ptr, result, narray->total);
    }
    return vresult;
}
   

.

前に戻る [目次]