container 教程
本教程演示如何用 container 算法在不同表示之间搬运矩阵和向量,如何在搬运途中改变元素类型,以及如何通过编写两个小型操作字典让你自己的容器类型参与进来。其背后的模型见 container 设计。
快速上手
moon add Luna-Flow/linear-algebra@0.5.0
///|
import {
"Luna-Flow/linear-algebra/container",
"Luna-Flow/linear-algebra/container/adapters" @container_adapters,
"Luna-Flow/linear-algebra/error" @la_error,
"Luna-Flow/linear-algebra/immut",
"Luna-Flow/linear-algebra/mutable",
}
把可变的工作矩阵冻结为不可变值:
///|
test "freeze a mutable matrix" {
let work = @mutable.Matrix::from_2d_array([[1, 2], [3, 4]])
let frozen : @immut.Matrix[Int] = @container.matrix_convert(
work,
@container_adapters.mutable_matrix_read_ops(),
@container_adapters.immutable_matrix_build_ops(),
).unwrap()
work.set(0, 0, 99)
inspect(frozen, content="|1, 2|\n|3, 4|")
}
之后对 work 的写入不会影响 frozen:转换会复制数据。
日常任务
转换并改变元素类型
matrix_map 一遍完成转换和变换;元素类型可以改变:
///|
test "integer pixels to normalized doubles" {
let pixels = @immut.Matrix::from_2d_array([[0, 128], [255, 64]])
let normalized : @mutable.Matrix[Double] = @container.matrix_map(
pixels,
@container_adapters.immutable_matrix_read_ops(),
@container_adapters.mutable_matrix_build_ops(),
p => p.to_double() / 255.0,
).unwrap()
inspect(normalized.get(1, 0), content="1")
inspect(normalized.get(0, 0), content="0")
}
转置到另一种表示
///|
test "transpose a dense wrapper into an immutable matrix" {
let source = @default.DenseMatrix::from_2d_array([[1, 2, 3]])
let column : @immut.Matrix[Int] = @container.matrix_transpose(
source,
@container_adapters.dense_matrix_read_ops(),
@container_adapters.immutable_matrix_build_ops(),
).unwrap()
inspect(column, content="|1|\n|2|\n|3|")
}
通过字典编辑
编辑字典是受检的:非法索引是一个错误值,且不会改变任何内容。
///|
test "checked edits" {
let m = @immut.Matrix::from_2d_array([[1, 2], [3, 4]])
let edit = @container_adapters.immutable_matrix_persistent_edit_ops()
let m2 = (edit.set)(m, 1, 1, 40).unwrap()
inspect(m2, content="|1, 2|\n|3, 40|")
inspect(m, content="|1, 2|\n|3, 4|")
match (edit.set)(m, 2, 0, 0) {
Err(e) => inspect(e.is_index_out_of_bounds(), content="true")
Ok(_) => fail("row 2 does not exist")
}
let row = @mutable.Matrix::from_2d_array([[5, 6, 7]]).row_view(0)
let view_edit = @container_adapters.mutable_row_view_mutable_edit_ops()
(view_edit.set)(row, 2, 70).unwrap()
inspect(row, content="|5, 6, 70|")
}
为自己的类型发布字典
以索引-值对存储的稀疏向量可以被读取和构造。只需编写这两个字典一次,之后每个泛型算法都能接受该类型:
///|
struct ContTutSparse {
len : Int
entries : Array[(Int, Double)]
}
///|
fn cont_tut_sparse_read() -> @container.VectorReadOps[ContTutSparse, Double] {
@container.VectorReadOps::new(v => v.len, (v, i) => {
guard i >= 0 && i < v.len else {
return Err(
@la_error.LinearAlgebraError::index_out_of_bounds("index \{i}"),
)
}
for entry in v.entries {
if entry.0 == i {
return Ok(entry.1)
}
}
Ok(0.0)
})
}
///|
fn cont_tut_sparse_build() -> @container.VectorBuildOps[ContTutSparse, Double] {
@container.VectorBuildOps::new((n, f) => {
guard n >= 0 else {
return Err(
@la_error.LinearAlgebraError::negative_dimension("length \{n}"),
)
}
let entries = []
for i in 0..<n {
let x = f(i)
if x != 0.0 {
entries.push((i, x))
}
}
Ok({ len: n, entries, })
})
}
///|
test "a sparse vector joins the generic algorithms" {
let sparse : ContTutSparse = { len: 5, entries: [(1, 2.0), (4, -1.0)], }
let dense : @immut.Vector[Double] = @container.vector_convert(
sparse,
cont_tut_sparse_read(),
@container_adapters.immutable_vector_build_ops(),
).unwrap()
inspect(dense, content="|0, 2, 0, 0, -1|")
let back : ContTutSparse = @container.vector_convert(
dense,
@container_adapters.immutable_vector_read_ops(),
cont_tut_sparse_build(),
).unwrap()
inspect(back.entries.length(), content="2")
}
进一步了解
发布哪些字典。 凡是可以被观察的类型都提供 read;凡是可以由函数构造的类型都提供 build;并按所有权恰好提供两种编辑形式之一:值类型提供持久化编辑,原地类型和视图提供可变编辑。集成指南列出了各个采用级别以及适配器代码应由谁负责。
需要测试的定律。 对你的字典,测试先 tabulate 再 get 会返回初始化函数的值,非法索引给出 IndexOutOfBounds,负形状给出 NegativeDimension,以及持久化 set 不改变其参数。设计页面把这些写成了等式。
性能。 这些算法通过闭包复制并缓冲整个源。它们用于边界处的转换,而不是数值代码的内层循环;在那里请使用具体类型自身的方法。
常见陷阱
- 忘记给字段调用加括号。 应写作
(ops.get)(v, i),而不是ops.get(v, i);这些字段是闭包,不是方法。 - 以为视图可以作为目标。 行、列和转置视图有读取字典和可变编辑字典,但没有构造字典。
- 把 当作 。 退化形状会被保留;请同时检查两个维度。
- 期望共享。 每个算法都会构造新的目标;之后对源的修改不会反映出来。
后续步骤
- container API 和 adapters API。
- container 设计:read/build 定律与 lens 定律。
- Container 集成指南:从其他库发布字典。