#array #file-format #storing #n-dimensional #hdf5 #ra #complex

rawarray

用于可检索存储n维数组的简单文件格式

2个版本

0.1.1 2020年1月5日
0.1.0 2020年1月5日

数学类中排名991

MIT许可证

27KB
264

欢迎使用RawArray库!

简介

RawArray是一个用于存储n维数组的简单文件格式。扩展名.ra可以发音为arr-ayrah(就像“raw”,或古埃及太阳神)。

RawArray被设计成可移植、快速且存储效率高。特别是对于科学应用,它可以在不使用单独的头文件来存储维度和类型元数据的情况下,简单地存储大型数组。

我认为世界不需要另一个分层数据容器。我们已经有一个了——它被称为文件系统。所需的是一种简单的数据结构到磁盘文件的映射,该映射保留元数据,并且读写速度快且简单。

除了任意大小的int、uint和float外,RawArray还支持

(1) 复数浮点数:这是其他常见格式,如HDF5所不具备的。

(2) 复合类型:RawArray可以处理这些类型的读写,但编码和解码留给用户,因为只有他们才知道自己的struct结构。对于固定大小的类型,解码可以像类型转换一样简单。在Rust中,它们被读取为Vec<T>,所以你可以自由地按需处理。

顺便说一下,RawArray格式在技术上来说是递归的(或者说是分形?!)。如果你想通过定义文件为复合类型,可以在一个RawArray文件中存储RawArray文件的数组。

格式

文件格式是由一个头数组和一个数据数组简单拼接而成的。头数组由至少七个64位无符号整数组成。数组数据可以是任何你想要的内容。可选地,可以在文件末尾附加文本或二进制元数据,这对文件没有害处,但库不会保存或写回这些数据。跟踪这些数据是你的责任。

文件结构

偏移量(字节) 对象 类型 含义
HEADER
0 魔数 UInt64 魔数
8 标志 UInt64 字节序,未来选项
16 eltype UInt64 元素类型代码
24 elbyte UInt64 元素大小(字节)
32 size UInt64 数据段长度(字节)
40 ndims UInt64 数组维度数量
48 dims Vector{UInt64} 数组维度
48 + 8 x ndims data Vector{UInt8} 数组数据
48 + 8 x ndims + size - - 易失性元数据

元素类型规范

代码 类型
0 用户定义
1 有符号整数
2 无符号整数
3 浮点数(IEEE-754标准)
4 复浮点数(IEEE浮点数的对)
5 脑浮点数

这些类型的宽度在elbyte字段中单独定义。例如,

  • 一个32位无符号整数将是eltype = 2elbyte = 4
  • 一个单精度复浮点数(32位浮点数的对)将是eltype = 4elbyte = 8
  • 一个字符串将是eltype = 2elbyte = 1,而size将包含字符串的长度。

用户定义的结构

struct foo {
   char info[12];
   uint32_t index;
   double v[8];
}

包含一个12字节的字符串,一个4字节的int和8个8字节的浮点数,所以总大小是80字节。它将被编码为eltype = 0elbyte = 80

数据以你在其上的硬件的二进制表示形式写入和读取。目前假设为小端字节序,但如果有兴趣,可以添加大端字节序的支持。

内存序

RawArray格式是列主序,因此第一个维度将是内存中变化最快的维度。这个决定是因为大多数科学语言传统上是列主序的,尽管C在技术上是以行为主序的,但实际上在通过计算线性索引访问多维数组的应用中是无关的(例如CUDA)。在提供的示例中,所有示例都是列主序的,除了Python。在Python的情况下,我们不是将数组读入Python并重新排序到非最优步长,而是在写入和读取之前简单地转置维度。这意味着在Python中数组看起来是转置的,但所有语言中的维度步长相同。换句话说,Python中数组的最后一个维度将是Julia和Matlab中的第一个维度。

文件内省

为了更好地了解RawArray文件格式,让我们看看一个文件内部的情况。如果你在Unix系统上或在Windows上安装了Cygwin,你可以使用命令行工具检查RawArray文件的内容。在本节中,我们将使用test.ra文件,该文件位于julia/子目录中。

首先,让我们假设你不知道数组的维度。然后

> od -t uL -N 48 test.ra
0000000              8746397786917265778              0
0000020              4                                8
0000040              96                               2
0000060

显示第三行第二个数字为维度(2)。该命令提取前48个字节并将它们格式化为UInt64。第一个列出的是荒谬的数字,表示这是一个RawArray文件。一个略微不同的命令揭示了这一点

> od -a -N 16 test.ra
0000000    r   a   w   a   r   r   a   y nul nul nul nul nul nul nul nul
0000020

凭借数组是2D的知识,我们知道头文件长度为48 + 2*8 = 64字节。跳过头文件只查看数据的命令将是

> od -j 64 -f test.ra
0000100     0.000000e+00            -inf    1.000000e+00   -1.000000e+00
0000120     2.000000e+00   -5.000000e-01    3.000000e+00   -3.333333e-01
0000140     4.000000e+00   -2.500000e-01    5.000000e+00   -2.000000e-01
0000160     6.000000e+00   -1.666667e-01    7.000000e+00   -1.428571e-01
0000200     8.000000e+00   -1.250000e-01    9.000000e+00   -1.111111e-01
0000220     1.000000e+01   -1.000000e-01    1.100000e+01   -9.090909e-02
0000240

在这里,我们使用-j跳过前64个字节,使用-f将字节数据格式化为单精度浮点数。注意od不理解复数,但复数数据以实部和虚部浮点对的形式存储在磁盘上,它们是连续的。这意味着输出中的每一行都显示了两个复数,列1和3是实部,列2和4是虚部。注意它正确地渲染了负无穷大。

获取

... TODO:解释cargo和添加一些二进制工具...

用法

在你的Rust源代码中的一个示例用法是

use rawarray::RawArray;
use std::io;
fn main() -> io::Result<()> {
	let vec1: Vec<f32> = vec![1.0, 2.0, 3.0, 4.0];
	let ra: RawArray<f32> = vec1.clone().into();
	ra.write("myarray.ra")?;

	let vec2: Vec<f32> = RawArray::<f32>::read("myarray.ra")?.into();
	assert_eq!(vec1, vec2);
	Ok(())
}

校验和和时间戳

数据校验和或时间戳被故意不包括在格式中,因为无法对包含其校验和的文件进行校验和。**现有的方法(例如tar)通常将校验和字段清零,然后校验文件其余部分,但这需要理解格式的特殊软件,因此标准命令行校验和工具将不起作用。校验和验证最好留给外部手段,即使它需要单独的文件。

时间戳也不必要,因为文件系统已经提供了这个功能。添加在重写或访问时更改的时间戳也会阻止校验和尝试。由于这个原因,HDF5文件校验和非常困难。我们相信,校验和应该只依赖于数据属性,而不是任何时间顺序。如果两个文件包含相同的数据,它们就是相同的,不管它们是在什么时候创建或最后访问的。

要校验和RawArray文件,只需运行你本地的校验和命令。例如,在Linux上

> md5sum examples/test.ra
1dd9f98a0d57ec3c4d8ad50343bd20cd  examples/test.ra

** 从技术上讲并非不可能,但计算上非常困难。

获取帮助

如有帮助,请在问题跟踪器上提交问题或发送电子邮件给作者之一。欢迎第三方帮助并通过拉取请求进行贡献。

作者

David S. Smith [email protected]

免责声明

此代码没有任何保证。自行承担风险使用。如果它损坏,请让我们知道,我们将尽力帮助您修复它。

依赖关系

~1.5MB
~31K SLoC