HDF5 : 構造体(COMPOUND型)の書込み2013年02月27日 22:02

構造体データの書込み

HDFViewをいじって、データの追加などしてみると、通常のDatasetの追加と異なり、CompoundDS(合成型)というものがあるのに気づくと思う。今回はこれの書込み。

このデータを書き込むには構造体が必要になる。ただ構造体を宣言するのではなく、structLayout属性を追加しなければならない。これを宣言しないと外部DLLに数値を渡せない。引数のlayoutKindはsequentialを選んで構造体の宣言順にデータにする。さらにパラメータにあるPackを宣言して1バイトずつパックする宣言を追加する。
[StructLayout(LayoutKind.Sequential,Pack = 1)]
struct TestStruct
{
   public int TestInt;
   public int TestDouble;
}
続いてHDF5ファイルの書込み準備。
必要なIDを準備する。
H5DataSpaceId :
 データの配列などの大きさを指定する。今までの記事を参照

H5DataTypeId :
 H5Tクラスにあるcreateメソッドを使用する。引数にH5TクラスにあるCreateClassの列挙より"COMPOUND"を選択する。もう一つの引数には一つのデータのサイズをバイト単位で指定する。もしint型とdouble型を合成型に書き込む場合は合計サイズ4+8=12バイトを指定する。
H5DataTypeId typeID = H5T.create( H5T.CreateClass.COMPOUND,12);
さらに、このDataTypeIdを使用して、データ領域を指定していく。
使用するメソッドはH5Tクラスにあるinsertメソッド。引数は以下の通り。
 compoundDataType : 上記のH5DataTypeIdを指定
 fieldName : string型。データの名前HDFViewで表示するときや、データを参照する際に使用する
 offset : int型。データが始まる位置。詳しくは下記の例を参照
 fieldId : H5DataTypeId型。挿入するデータの型。
使用方法は下記のようにint型を挿入する場合はfieldId部には"NATIVE_INT"の列挙を使用して作成したH5DataTypeIdクラスを、double型を使用する場合は"NATIVE_DOUBLE"の列挙など、書き込むデータに合わせてH5DataTypeIdを作成する。
offsetには最初は0を指定すればいいが、次に挿入を行う場合には前回までのデータをサイズを足して書いていかなければならない。もちろん上記のCompoundのH5DataTypeIdを指定した時のサイズを超えてはならない。
H5DataTypeId intTypeId = H5T.copy(H5T.H5Type.NATIVE_INT);
H5T.insert(typeID, "IntTestData", 0, intTypeId);
H5DataTypeId doubleTypeId = H5T.copy(H5T.H5Type.NATIVE_DOUBLE);
H5T.insert(typeID, "DoubleTestData", 4, doubleTypeId);
上記の"H5T.insert"メソッドにて、1番目に使用する"H5DataTypeId"を使用して"H5DataSetId"を作成し、H5Arrayクラスを作成して書き込む。write時に使用するジェネリックの型はもちろん使用する配列型を使用する。
H5DataSetId setID = H5D.create(fileID, "COMPOUND", typeID, spaceID);
TestStruct testData = new TestStruct();
testData.TestDouble = 0.123456;
testData.TestInt = -123;
TestStruct[] testArray = new TestStruct[]{testData};
H5Array<TestStruct> array = new H5Array<TestStruct>(testArray); H5D.write<TestStruct>(setID, typeID, array);
あとは毎度のようにIDの解放処理を行う。

HDF5DotNet : Enumデータの書込・読込2013年02月19日 22:24

ENUMデータの書込み、読込みについて。

ENUMデータを書き込むには、"H5T"クラスにある"createEnum"メソッドを使用して"H5DataTypeID"クラスを作成する。引数は"H5T.H5Type"の列挙なので、適当に指定。NATIVE_INTなどでいいと思う。
H5DataTypeId typeID = H5T.enumCreate( H5T.H5Type.NATIVE_INT );
次に、書き込むデータとHDFViewなどを使用した場合に表示されるデータを準備する。使うメソッドは"H5T"クラスにある"enumInsert"のジェネリックメソッド。
引数は以下の通り
 "typeId" : "H5DataTypeId"クラス、上記のメソッドで取得したものを使用する
 "name" : "string"クラス 、表示する名前
 "value" : "ref int"クラス 、名前表示と関係させる値
例えば、bool値"True"と"False"を作成する場合は、以下のようにする。
int f_index = 0;
int t_index = 1;
H5T.enumInsert<int>( typeID, "FALSE"ref f_index ); H5T.enumInsert<int>( typeID, "TRUE"ref t_index );
あとは"H5DataSetId"クラスを作成して、"write<>"してやればいい。

値を読み込む場合は、値型を読み込む方法を行えば、数値が取得できる。
その後、"H5DataTypeId"クラスを使用して表示名を取り出す。取り出す際に使用するメソッドは"H5T"クラスにある"enumNameOf"というジェネリックメソッドを使用する。引数、戻り値は以下の通り。
 "typeId" : "H5DataTypeId"クラス、読み込み時に使用したもの
 "value" : "ref int"クラス、データと関連付けた値を使用。要は読み取り時に取得した値。
 戻り値:"string"クラス。関連付けた名前
string name = H5T.enumNameOf<int>( rTypeID, ref index );
戻ってきた値を調べて、再びenum値に戻してやればC#上で使用できるようになる。
以下、サンプル。
//	書込みサンプル
H5FileId
 fileID = H5F.open( "C:\\HDFSampleFile.hdf5"H5F.OpenMode.ACC_RDWR ); H5DataSpaceId spaceID = H5S.create_simple( 1new long[] { 1 } ); H5DataTypeId typeID = H5T.enumCreate( H5T.H5Type.NATIVE_INT );
H5DataSetId
 dataSetID = H5D.create( fileID,"ENUMDataSet",typeID ,spaceID  ); int[] intArray = new int[] { 1 }; H5Array<int> array = new H5Array<int>( intArray ); H5D.write<int>( dataSetID, dataTypeId, array ); H5D.close( dataSetID ); H5T.close( dataTypeId );
H5S.close( spaceID ); H5F.close( fileID );
//	読み込みサンプル
H5FileId
fileID = H5F.open( "C:\\HDFSampleFile.hdf5", H5F.OpenMode.ACC_RDWR ); H5DataSetId datasetID = H5D.open( fileID, "ENUMDataSet" );
H5DataTypeId typeId = H5D.getType( datasetID ); int[] intArray = new int[1];
H5Array<int> array = new H5Array<int>( intArray );
H5D.read<int>( datasetID , typeId , new H5Array<int>( array ) ); int index = intArray[ 0 ];
// これで文字列で関連付けた表示が取得される。
string name = H5T.enumNameOf<int>( typeId , ref index );
H5D.close( dataSetID ); H5T.close( typeId );
H5F.close( fileID );

 
 

プログラムでの値型の精度とか2013年02月15日 11:33

HDF5DotNetとは別でプログラムの話。

プログラムで、多項式近似の係数を出力する機能がほしくなったので、適当にネットで転がっている情報をもとに作成。参考ページとできたのがこれ

エクセルを用いた最小二乗法 : http://homepage1.nifty.com/gfk/square_ren.htm
逆行列の求め方 : http://thira.plavox.info/blog/2008/06/_c.html

プログラム
//行列の作成
dimWithIntercept = 5; double[ , ] matrix = new double[ dimWithIntercept, dimWithIntercept]; double[ , ] copy = new double[ dimWithIntercept, dimWithIntercept ]; double[ , ] revMatrix = new double[ dimWithIntercept, dimWithIntercept ]; double[] vector = new double[dimWithIntercept]; List<double> ret = new List<double>( ); int digit = 15; MidpointRounding rounding = MidpointRounding.AwayFromZero; forint i = 0 ; i < xyValues.Count ; i++ ) { // 行列の作成 forint j = 0 ; j < dimWithIntercept ; j++ ) { forint k = 0 ; k < dimWithIntercept ; k++ ) { matrix[ j, k ] = matrix[ j, k ] + Math.Pow( xyValues[ i ].Key, j ); copy[ j, k ] = matrix[ j, k ]; } //ベクトルの作成 vector[ j ] = vector[ j ] + Math.Pow(xyValues[ i ].Key, j ) ; } } // 逆行列の作成 // まずは単位行列を作る forint i = 0 ; i < dimWithIntercept ; i++ ) { forint j = 0 ; j < dimWithIntercept ; j++ ) { revMatrix[ i,j ] = (i == j) ? 1.0 : 0.0; } }
// 吐き出し法 double buf; forint i = 0 ; i < dimWithIntercept ; i++ ) { // 単位化するために、対になる値で割る buf = copy[ i, i ]; // iの行を単位化するためにbufで割る forint j = 0 ; j < dimWithIntercept ; j++ ) { copy[ i, j ] = copy[ i, j ] / buf; revMatrix[ i, j ] = revMatrix[ i, j ] / buf; } forint j = 0 ; j < dimWithIntercept ; j++ ) {
// iと同じ行では計算しない if( i != j ) { buf = copy[ j, i ]; //n列目の値を0にする(n,nの場所はスルー) forint k = 0 ; k < dimWithIntercept ; k++ ) { double a = copy[ i, k ] * buf; copy[ j, k ] = copy[ j, k ] - a;
a = revMatrix[ i, k ] * buf; revMatrix[ j, k ] =revMatrix[ j, k ] - a;
} }
} }
とりあえずテストしてみて、エクセルのLINEST関数の結果と照らし合わせるなどして比較。いけそうだと思ったがうまくいかない場合がでてきた。その場合とは小数点の値を使う場合。たとえば、x = 0.1、0.2、0.3 を使用しての4次元の多項式y=a * x^4 + b * x^3 + c * x^2 + d * x^1 + e の各a,b,c,d,eを求めるために、各項を2乗、3乗としていったが、デバッグで値を見ると、後ろに妙な端数がある。
これは、浮動小数点型を使用して計算を行うとどうしても誤差がでるらしい。
http://dobon.net/vb/dotnet/beginner/floatingpointerror.html

多少誤差が出ようが、似たような計算結果になってほしいところだが、実際に上記のプログラムを使用して算出された値を使い、使用したx値を与えてやっても同じようなy値が算出できなかった。

なので、結局エクセルのLinEstクラスを使用するようにした。
使用するには、HDF5DotNetの時と同じく、参照の追加を行わなければならない。追加するものは、COMにある"Microsoft Office xx(xxはofficeのバージョン)Object Library"。これを追加すると、ソリューションエクスプローラーの中の参照設定の中に"Microsoft.Office.Core"と"Microsoft.Office.Interop.Excel"が追加される。

これでエクセルの編集などができるようになったが、あくまで目的はワークシート関数の"LinEst"を使用すること。

とりあえず、簡単に以下のラッパークラスを作成した。
static public  class CoefficientsClass
{
    static Excel.Application oXL = new Excel.Application( );
    static Excel.WorksheetFunction funk = oXL.WorksheetFunction;
    
public static Array GetCoefficients(Array y, Array x, bool a, bool b) { System.Object coefficients = funk.LinEst( y, x, a, b ); Array coeffArray = (Array)coefficients; return coeffArray; } }
説明するとstaticでエクセル起動とワークシート関数をクラスに置いておく。
データを持ってきてLinEst関数を使用する。ただ単にラップしただけ。
あとはこれを使うだけ
double[ , ] x = new double[ dim , xyValues.Count ];
double[] y = new double[ xyValues.Count ];
forint i = 0 ; i < xyValues.Count ; i++ ) {
    forint j = 0 ; j < dim ; j++ ) {
	x[ j, i ] = Math.Round(Math.Pow(xyValues[i].Key,j + 1),
15,MidpointRounding.AwayFromZero); } y[ i ] = xyValues[ i ].Value; } Array coeffArray = CoefficientsClass.GetCoefficients( y, x ,true,false); List<double> ret = new List<double>(); for(int i = 1 ; i < coeffArray.Length + 1 ; i++){ ret.Add( (double)coeffArray.GetValue( i ) ); }
xを配列単体で行うとy=ax+bの係数と切片が得られる。xを上記のように二次配列にして、ほしい次元まで乗数倍したx値を用意すれば、多項近似式も得ることができる。

HDF5 : 可変長文字列の書き込み2013年02月14日 21:35

可変長な文字列の書き込みと読み込みについて
文字の書き込みと読み込みについては、一度固定長の文字列について言及したが、今回は可変長の文字列について

書き込みには"H5DataSetId"クラス("H5DataSetId"クラスを作成するには”H5DataSpaceId”クラスと"H5DataTypeId"クラスが必要)と"H5DataTypeId"クラスが必要になる点はかわらない。
違う点について、
 固定長時は文字列を文字列データとして扱うために、byte配列に変換、変換データを用いて"H5DataType"クラスのサイズ設定と”write<>”メソッドをbyte型にて実行した。
 可変長時はC++などで使用するポインタを使用(といってもポインタを意識するような使い方はしなくてもよい)して、"H5DataSetId"クラスの作成や"write<>"メソッドの実行を行う。

"H5DataSpaceId"クラスの作成については、今までと変わらないので、固定長のものを参考に、ここでは割愛。

"H5DataTypeId"クラスを作成する場合は、"H5T"クラスの"create"メソッドを使用するが、引数の"size"(int型)に-1を指定する。これは、可変長の文字列を使用するときには必ず行う。
H5DataTypeId typeID = H5T.create(H5T.CreateClass.STRING,-1);
"H5DataSetId"クラスは上記の"H5DataSpaceId"クラスと"H5DataTypeId"で作成。作成方法は今までと同じなので割愛。

次に文字列のデータの作成。文字列データへの変換には"UTF8Encoding"クラスなどのEncoding系クラスを使用せず、System.Runtime.InteropServices空間にある"Marshal"クラスにある"StringToHGlobalAnsi"メソッドを使用する。引数は"string"クラス、戻り値は"IntPtr"構造体。要するに、アンマネージ領域に文字列データを用意しておき、ポインタを使用して書き込む。
IntPtr stringPtr = Marshal.StringToHGlobalAnsi( stringValue );
これで、"H5D"クラスの"write<>"メソッドを実行する。ジェネリックの指定はもちろん"IntPtr"構造体を使用する。
H5Array<IntPtr> dataArray = new H5Array<IntPtr>( new []{stringPtr} );
H5D
.write<IntPtr>( datasetID, ftypeID, dataArray ) );
使用後は、"close"メソッドができるものは行う。"IntPtr"構造体については"Marshal"クラスにある"FreeHGlobal"メソッドを使用してメモリを開放する。引数は"IntPtr"構造体。
Marshal.FreeHGlobal( stringPtr );
以下、サンプル。
H5FileId fileID = H5F.create("C:\\Test.hdf5" , H5F.CreateMode.ACC_TRUNC );
H5DataTypeId typeID = H5T.create(H5T.CreateClass.STRING,-1); H5DataSetId datasetID = H5D.create( fileID, "VariableStr", typeID, spaceID ); string stringValue = "This is TestString.";
IntPtr stringPtr = Marshal.StringToHGlobalAnsi( stringValue );
H5Array<IntPtr> dataArray = new H5Array<IntPtr>( new []{stringPtr} );
H5D
.write<IntPtr>( datasetID, typeID, dataArray ) );
H5D.close( datasetID );
H5T.close( typeID ); 
H5S.close( spaceID );
H5F.close( fileID );
Marshal.FreeHGlobal( stringPtr );

HDF5DotNet : 文字列の読み込み2013年02月13日 22:59

HDF5ファイルから文字列データの読み込み方法

文字列の読み込みについても、数値の読み込みと同様の手順を踏めば取り込むことができる。少し違うのが、取得する文字列のサイズを知らなければならない。

まずは、数値の読み込みと同様に、"H5FileId"クラス、もしくは"H5GroupId"と、DataSetの名前を使用して、"H5DataSetId"クラスを取り出す。
その後、取り出した"H5DataSetId"クラスより、"H5DataTypeId"クラスを得る。
H5DataSetId dataSetID = H5D.open( fileID,"TestStringDataSet" );
H5DataTypeId dataTypeID = H5D.getType( dataSetID );
次に文字列データのサイズを取得する。
以前に、文字列データの書き込みについて記述したように、書き込み時は文字列を"byte"型の配列に変換して保存した。なので、読み込み時も"byte"型の配列を得てから変換を行う必要がある。
まずは、"byte"型配列の大きさを取得する。取得するには、"H5T"クラスにあるメソッド"getSize"を使用する。引数は"H5DataTypeId"、戻り値は"int"型。
byte[] byteArray = new byte[ H5T.getSize(dataTypeID)];
あとは値型の時と同じように"H5D"クラスの"read<>"メソッドでデータを読み込むジェネリックには"byte"型を指定する。
H5Array<byte> array = new H5Array<byte>( byteArray );
H5D.read<byte>( dataSetID , dataTypeID , array );
これでbyteArrayのインスタンスに文字列データが格納されているので、"System.Text.Encoding"クラスにある"GetString"メソッドでデータを変換してやる。使用するエンコーディングは、保存時と同じものを使用する。
string testString = Syste.Text.Encoding.UTF8.GetString(byteArray);
これで文字列の取得は完了。あとはおなじみの"close"メソッドを使用する。
以下、サンプル
H5FileId fileID = H5F.open( "C:\\HDFSampleFile.hdf5", H5F.OpenMode.ACC_RDWR );
H5DataSetId dataSetID = H5D.open( fileID,"TestDataSet" );
H5DataTypeId dataTypeId = H5D.getType( dataSetID );
byte[] byteArray = new byte[ H5T.getSize(dataTypeID)];
H5Array<byte> array = new H5Array<byte>( byteArray );
H5D.read<byte>( dataSetID , dataTypeID , array );
string testString = Syste.Text.Encoding.UTF8.GetString(byteArray);
H5D.close( dataSetID ); H5T.close( dataTypeId ); H5F.close( fileID );