h5

使い方

次元順序を確認してから、目的に合うコードを選びます。言語の選択はページ全体に適用され、次回も記憶されます。JavaScript が無効でも全言語を読めます。

配列の次元順序

Fortran の a(nx, ny) と C の dims = {ny, nx} は、同じファイル内の同じバイト列を表します。Fortran は最初の添字、C/C++ は最後の添字が最も速く変わります。次元の表記を逆順に対応させるだけで、転置も、そのためのコピーも不要です。

h5c/docs/FORMAT.md 冒頭の h5dump による確認結果は次のとおりです。Fortran の (2, 3) がファイル上では (3, 2) になり、値の順序は変わりません。

Fortran: real(real64) :: a(2, 3)   ! 値 1..6 を reshape で格納
HDF5:    shape = (3, 2)
         (0,0): 1, 2
         (1,0): 3, 4
         (2,0): 5, 6

下の実コードも同じ 6 要素の配置です。

Fortran — 2 次元配列を初期化します。

use, intrinsic :: iso_fortran_env, only: real64
implicit none
real(real64) :: values(2, 3)

values = reshape([1.0_real64, 2.0_real64, 3.0_real64, &
                  4.0_real64, 5.0_real64, 6.0_real64], shape(values))

参考:h5fortran/test/test-serial.F90。

C — 平坦なバッファとファイル上の shape。

const double values[6] = { 1, 2, 3, 4, 5, 6 };
const size_t dims[2]   = { 3, 2 };

参考:h5c/example/serial/main.c(完全なプログラム)。

C++ — C と同じ次元順序。

const std::vector<double> values = { 1, 2, 3, 4, 5, 6 };
const h5cpp::shape        dims   = { 3, 2 };

参考:h5cpp/example/serial/main.cpp(完全なプログラム)。

以下は操作ごとのコード例です。配列の宣言・初期化は用途に合わせて用意してください。各コード下のリンクは、関連する実装例を調べるための参考です。

1. ファイルを開いて配列を書く

新規作成モードは同名ファイルを切り詰めます。保存先を確認してください。

Fortran — 主要部分の抜粋です。

call h5open_f(hdferr)
if (hdferr /= 0) error stop "h5open_f failed"
call h5fcreate_f("serial.h5", H5F_ACC_TRUNC_F, file_id, hdferr)
if (hdferr /= 0) error stop "h5fcreate_f failed"

call h5fort_swrite(file_id, "/values", values, hdferr)
if (hdferr /= 0) error stop "h5fort_swrite failed"

call h5fclose_f(file_id, hdferr)
if (hdferr /= 0) error stop "h5fclose_f failed"
call h5close_f(hdferr)
if (hdferr /= 0) error stop "h5close_f failed"

参考:h5fortran/example/serial/main.F90(完全なプログラム)。

C — 書き込み後は全体コードの h5c_file_status と h5c_close の戻り値を確認します。

if (failed("open", h5c_open(path, H5C_TRUNCATE, &f))) {
    return 1;
}

h5c_write_f64(f, "/mesh/coords", values, 2, dims);

参考:h5c/example/serial/main.c(完全なプログラム)。

C++ — 型と要素数はコンテナから取得します。

h5cpp::file f(path, h5cpp::mode::truncate);

f.write("/mesh/coords", values, dims);

f.close();

参考:h5cpp/example/serial/main.cpp(完全なプログラム)。

RAII によりスコープ終了時に閉じます。デストラクタは例外を投げないため、書き込みの終了エラーを検出するには明示的に close() を呼びます。

2. 配列を読む

保存された shape に合う受け取り先を用意します。

Fortran — fields.h5 の 1 次元の圧力と 2 次元の座標を読みます。

use hdf5
use h5fort, only: h5fort_sread
use, intrinsic :: iso_fortran_env, only: real64
implicit none
integer(hid_t) :: file_id
integer :: hdferr
real(real64), allocatable :: pressure(:), coords(:, :)

call h5open_f(hdferr)
if (hdferr /= 0) error stop "HDF5 の初期化に失敗"
call h5fopen_f("fields.h5", H5F_ACC_RDONLY_F, file_id, hdferr)
if (hdferr /= 0) error stop "ファイルを開けません"
call h5fort_sread(file_id, "/pressure", pressure, hdferr)
if (hdferr /= 0) error stop "圧力の読み込みに失敗"
call h5fort_sread(file_id, "/coords", coords, hdferr)
if (hdferr /= 0) error stop "座標の読み込みに失敗"
call h5fclose_f(file_id, hdferr)
if (hdferr /= 0) error stop "ファイルを閉じられません"
call h5close_f(hdferr)
if (hdferr /= 0) error stop "HDF5 の終了に失敗"

参考:h5fortran/test/test-serial.F90。

h5fort_sread は保存された shape に合わせて allocatable 配列を確保します。

C — 全体コードでは先に H5C_READ で開き、読み込み後に free(got) で解放します。

if (failed("info", h5c_dataset_info(f, "/mesh/coords", &info))) {
    h5c_close(f);
    return 1;
}

got = (double *)malloc(info.count * sizeof *got);
if (got == NULL) {
    h5c_close(f);
    return 1;
}

if (failed("read", h5c_read_f64(f, "/mesh/coords", got,
                                info.rank, info.dims))) {
    free(got);
    h5c_close(f);
    return 1;
}

参考:h5c/example/serial/main.c(完全なプログラム)。

C++ — 保存 shape に合わせて読み込みます。

h5cpp::file f(path, h5cpp::mode::read);

const std::vector<double> got = f.read<double>("/mesh/coords");

参考:h5cpp/example/serial/main.cpp(完全なプログラム)。

read<T>() は必要な長さの std::vector を返します。失敗は h5cpp::error 例外です。全体コードの catch を参照してください。

3. 可視化用に出力する

メッシュ・座標・物理量・時刻を HDF5 に記録します。1 ステップ 1 ファイルで出力し、h5xdmf で XDMF に変換して ParaView で開きます。

Fortran — 四面体メッシュと節点データ。逐次の writer は t_hdf5_writer です(並列は t_phdf5_writer、タスク 6)。

type(t_hdf5_writer) :: fluid

fluid%h5_filepath = trim(filename)
fluid%output_type = "UnstructuredGrid"
fluid%mesh_name = "fluid"
fluid%topology_type = "Tetrahedron"
fluid%nodes_per_element = 4
fluid%num_points = fluid_points
fluid%num_cells = fluid_cells
fluid%time = time
call fluid%init()
call fluid%write_geometry(fluid_nodes, fluid_connectivity)
call fluid%write_point_data(pressure, "Pressure")
call fluid%write_point_data(velocity, "Velocity")

call fluid%close()

参考:h5fortran/example/serial-viz/main.F90(完全なプログラム)。

C — この抜粋の前に mesh の種類・名前・節点数・要素数を設定します。実行前に result/ を作成してください。

must(h5c_viz_open(path, t, &viz), "viz_open");

must(h5c_viz_begin_mesh(viz, &mesh), "begin_mesh fluid");

must(h5c_viz_write_nodes(viz, nodes, H5C_F64), "write_nodes");
must(h5c_viz_write_connectivity(viz, conn, H5C_I32), "write_conn");

must(h5c_viz_write_point_data(viz, "Pressure", pressure, H5C_F64, 1),
     "point Pressure");

参考:h5c/example/serial-viz/main.c(完全なプログラム)。

must はこの例のエラー確認関数です。全体コード末尾で status の確認と close を行います。

C++ — 三角形メッシュの例です。

h5cpp::viz writer(path, time);
writer.begin_mesh({h5cpp::viz_kind::unstructured, "fluid",
                   "Triangle", 3, x.size(), 2});
writer.write_nodes<double>({x, y, z});
writer.write_connectivity(connectivity);

writer.write_point_data("Pressure", pressure);
writer.write_point_data<double>("Velocity", {x, y, z});

参考:h5cpp/example/serial-viz/main.cpp(完全なプログラム)。

成分別の {x, y, z} は h5cpp::span の非所有ビューとして渡します。ビュー自体は配列をコピーしません。呼び出し中は元の vector を有効に保ちます。

4. 並列で書く

各ランクの局所配列を 1 ファイルに連結します。全ランクが同じ順序・同じパスで並列 API を呼びます。

分割軸はファイルの第 0 次元です。C/C++ では dims[0]、Fortran では配列の最終次元で、同じ軸を指します。他の次元の長さは全ランクで揃えます。パス P の下に P/data と P/__partition__ が保存されます。後者は長さ nprocs + 1 の int64 境界配列で、ランク r は境界 r から r+1 の直前までを担当します。先頭は 0、末尾は data の第 0 次元の長さです。

Fortran — MPI と HDF5 の初期化後、MPI 用のファイルアクセス設定を渡します。

call h5pcreate_f(H5P_FILE_ACCESS_F, fapl_id, hdferr)
call h5pset_fapl_mpio_f(fapl_id, MPI_COMM_WORLD, MPI_INFO_NULL, hdferr)
call h5fcreate_f("parallel.h5", H5F_ACC_TRUNC_F, file_id, hdferr, access_prp=fapl_id)
call h5pclose_f(fapl_id, hdferr)
call h5fort_pwrite(file_id, "/values", local_values, hdferr)
if (hdferr /= 0) call MPI_Abort(MPI_COMM_WORLD, 1, ierr)

参考:h5fortran/example/parallel/main.F90(完全なプログラム)。

C — 全体コードでは h5c_popen(path, H5C_TRUNCATE, &f) で開き、各戻り値を検査します。

nlocal  = (size_t)(2 + me);
dims[0] = nlocal;   /* the split axis */
dims[1] = 3;        /* must be the same on every rank */

st = h5c_pwrite(f, "/coords", local, H5C_F64, 2, dims, H5C_WRITE_DEFAULT);

参考:h5c/example/parallel/main.c(完全なプログラム)。

C++ — ランクごとに異なる行数を渡します。

const std::size_t nlocal = static_cast<std::size_t>(2 + me);
const h5cpp::shape dims = {nlocal, 3};  // dims[0] is the split axis

h5cpp::parallel_file f(path, h5cpp::mode::truncate);
f.write("/coords", local, dims);

参考:h5cpp/example/parallel/main.cpp(完全なプログラム)。

local は std::vector<double> です。RAII の破棄・明示的な close は MPI の終了前に、全ランクで揃えて行います。

5. 並列で読む

ここでは書き込み時と同じランク数で、保存された境界に従って各ランクの担当分を読み戻します。全ランクが同じ順序で呼び出してください。

Fortran — MPI と HDF5 の初期化後、fields-parallel.h5 の /pressure と /coords から局所データを読みます。両パスは h5fort_pwrite で保存したものとします。

use hdf5
use h5fort, only: h5fort_pread, h5fort_pread_fixed
use mpi
use, intrinsic :: iso_fortran_env, only: real64
implicit none
integer(hid_t) :: file_id, fapl_id
integer :: hdferr, ierr
real(real64), allocatable :: pressure(:), coords(:, :)
real(real64), allocatable :: pressure_buffer(:), coords_buffer(:, :)

call h5pcreate_f(H5P_FILE_ACCESS_F, fapl_id, hdferr)
if (hdferr /= 0) call MPI_Abort(MPI_COMM_WORLD, 1, ierr)
call h5pset_fapl_mpio_f(fapl_id, MPI_COMM_WORLD, MPI_INFO_NULL, hdferr)
if (hdferr /= 0) call MPI_Abort(MPI_COMM_WORLD, 1, ierr)
call h5fopen_f("fields-parallel.h5", H5F_ACC_RDONLY_F, file_id, hdferr, access_prp=fapl_id)
if (hdferr /= 0) call MPI_Abort(MPI_COMM_WORLD, 1, ierr)
call h5pclose_f(fapl_id, hdferr)
if (hdferr /= 0) call MPI_Abort(MPI_COMM_WORLD, 1, ierr)
call h5fort_pread(file_id, "/pressure", pressure, hdferr)
if (hdferr /= 0) call MPI_Abort(MPI_COMM_WORLD, 1, ierr)
allocate(pressure_buffer(size(pressure)), stat=ierr)
if (ierr /= 0) call MPI_Abort(MPI_COMM_WORLD, 1, ierr)
call h5fort_pread_fixed(file_id, "/pressure", pressure_buffer, hdferr)
if (hdferr /= 0) call MPI_Abort(MPI_COMM_WORLD, 1, ierr)
call h5fort_pread(file_id, "/coords", coords, hdferr)
if (hdferr /= 0) call MPI_Abort(MPI_COMM_WORLD, 1, ierr)
allocate(coords_buffer(size(coords, 1), size(coords, 2)), stat=ierr)
if (ierr /= 0) call MPI_Abort(MPI_COMM_WORLD, 1, ierr)
call h5fort_pread_fixed(file_id, "/coords", coords_buffer, hdferr)
if (hdferr /= 0) call MPI_Abort(MPI_COMM_WORLD, 1, ierr)
call h5fclose_f(file_id, hdferr)
if (hdferr /= 0) call MPI_Abort(MPI_COMM_WORLD, 1, ierr)

参考:h5fortran/test/test-parallel.F90。

pread は出力の allocatable 配列を確保し、pread_fixed は確保済み配列に読み込みます。ここでは最初の読み込みで局所 shape を取得してバッファを確保しています。局所 shape が分かっていれば、最初から確保済みのバッファを渡せます。ファイルを閉じた後に HDF5 と MPI を終了します。

C — 抜粋はエラー時の分岐までを示します。

if ((st = h5c_popen(path, H5C_READ, &f)) != H5C_OK) {
    MPI_Abort(MPI_COMM_WORLD, 1);
}

if ((st = h5c_pread(f, "/coords", got, H5C_F64, 2, dims)) != H5C_OK) {
    if (me == 0) {
        fprintf(stderr, "pread: %s (%s)\n", h5c_status_string(st),
                h5c_last_error()->message);
    }
    MPI_Abort(MPI_COMM_WORLD, 1);
}

参考:h5c/example/parallel/main.c(完全なプログラム)。

全体コードでは dims = {nlocal, 3} に相当する shape と、その要素数分の got を事前に用意しています。境界と現在のランク数が一致する必要があります。

C++ — 保存された境界から受け取りサイズを決めます。

h5cpp::parallel_file f(path, h5cpp::mode::read);

const std::vector<double> got = f.read<double>("/coords");

参考:h5cpp/example/parallel/main.cpp(完全なプログラム)。

戻り値は局所データの std::vector です。info() や partition() も集団呼び出しなので、ランク 0 だけに限定しないでください。

6. 並列で可視化用に出力する

全ランクが同じ時刻・メッシュの順序で局所データを書きます。connectivity はランク内の 0 始まりの節点番号を渡し、保存時に全体の番号へ補正されます。出力後の XDMF への変換手順は逐次出力と共通です。

Fortran — 並列用の writer に局所の節点数・要素数を設定します。

type(t_phdf5_writer) :: fluid, soil

fluid%h5_filepath = trim(filename)
fluid%output_type = "UnstructuredGrid"
fluid%mesh_name = "fluid"
fluid%topology_type = "Tetrahedron"
fluid%nodes_per_element = 4
fluid%num_points = fluid_points
fluid%num_cells = fluid_cells
fluid%time = time
call fluid%init()
call fluid%write_geometry(fluid_nodes, fluid_connectivity)
call fluid%write_point_data(pressure, "Pressure")

call fluid%close()

参考:h5fortran/example/parallel-viz/main.F90(完全なプログラム)。

C — mesh は局所の節点数・要素数で設定します。出力先ディレクトリは事前に作成してください。

must(h5c_viz_popen(path, t, MPI_COMM_WORLD, MPI_INFO_NULL, &viz),
     "viz_popen");

must(h5c_viz_begin_mesh(viz, &mesh), "begin_mesh fluid");

must(h5c_viz_write_nodes(viz, nodes, H5C_F64), "write_nodes");
must(h5c_viz_write_connectivity(viz, conn, H5C_I32), "write_conn");

must(h5c_viz_write_point_data(viz, "Pressure", pressure, H5C_F64, 1),
     "point Pressure");

参考:h5c/example/parallel-viz/main.c(完全なプログラム)。

must はこの例の補助関数で、失敗時には全ランクを停止します。

C++ — 局所メッシュと物理量を渡します。

h5cpp::parallel_viz writer(path, time);

writer.begin_mesh({h5cpp::viz_kind::unstructured, "fluid", "Tetrahedron",
                   4, npoints, kCells});
writer.write_nodes(nodes);
writer.write_connectivity(connectivity);
writer.write_point_data("Pressure", pressure);
writer.write_point_data("Velocity", velocity, 3);
writer.write_cell_data("SubdomainID", subdomain);

参考:h5cpp/example/parallel-viz/main.cpp(完全なプログラム)。

並列 writer でも vector の長さを検査します。全体コードには例外の捕捉とランク間の終了状態の集約が含まれます。