parent
a078cbc7fb
commit
3c5f9a04dd
@ -1,2 +1,4 @@ |
|||||||
node_modules |
node_modules |
||||||
build |
build |
||||||
|
data |
||||||
|
models |
||||||
|
|||||||
@ -0,0 +1,37 @@ |
|||||||
|
struct MatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<f32>, |
||||||
|
} |
||||||
|
|
||||||
|
struct FVectorBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<f32>, |
||||||
|
} |
||||||
|
|
||||||
|
struct UVectorBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<u32>, |
||||||
|
} |
||||||
|
|
||||||
|
@group(0) @binding(0) |
||||||
|
var<storage, read> ps: MatrixBatch; |
||||||
|
@group(0) @binding(1) |
||||||
|
var<storage, read_write> result: UVectorBatch; |
||||||
|
|
||||||
|
@compute @workgroup_size(1) |
||||||
|
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { |
||||||
|
result.size.x = ps.size.y; |
||||||
|
result.size.z = ps.size.z; |
||||||
|
|
||||||
|
var mi = 0u; |
||||||
|
var mp = 0f; |
||||||
|
var pi = global_id.z * ps.size.y * ps.size.x + global_id.x * ps.size.x; |
||||||
|
for (var i = 0u; i < ps.size.x; i++) { |
||||||
|
if (ps.data[pi] > mp) { |
||||||
|
mi = i; |
||||||
|
mp = ps.data[pi]; |
||||||
|
} |
||||||
|
pi++; |
||||||
|
} |
||||||
|
result.data[global_id.z * result.size.x + global_id.x] = mi; |
||||||
|
} |
||||||
@ -0,0 +1,33 @@ |
|||||||
|
struct PackedMatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<vec4<f32>>, |
||||||
|
} |
||||||
|
|
||||||
|
struct MatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<f32>, |
||||||
|
} |
||||||
|
|
||||||
|
@group(0) @binding(0) |
||||||
|
var<storage, read> e: PackedMatrixBatch; |
||||||
|
@group(0) @binding(1) |
||||||
|
var<storage, read> tw: PackedMatrixBatch; |
||||||
|
@group(0) @binding(2) |
||||||
|
var<storage, read_write> aw: MatrixBatch; |
||||||
|
|
||||||
|
@compute @workgroup_size(1) |
||||||
|
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { |
||||||
|
aw.size.x = tw.size.y; |
||||||
|
aw.size.y = e.size.y; |
||||||
|
aw.size.z = e.size.z; |
||||||
|
|
||||||
|
var s = 0f; |
||||||
|
let vc = e.size.x / 4; |
||||||
|
let lo = (global_id.z * e.size.y + global_id.y) * vc; |
||||||
|
let ro = (global_id.z * tw.size.y + global_id.x) * vc; |
||||||
|
for (var i = 0u; i < vc; i++) { |
||||||
|
s += dot(e.data[lo + i], tw.data[ro + i]); |
||||||
|
} |
||||||
|
|
||||||
|
aw.data[(global_id.z * aw.size.y + global_id.y) * aw.size.x + global_id.x] = s; |
||||||
|
} |
||||||
@ -0,0 +1,32 @@ |
|||||||
|
alias number = f32; |
||||||
|
|
||||||
|
struct MatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<vec4<number>>, |
||||||
|
} |
||||||
|
|
||||||
|
struct Matrix { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<vec4<number>>, |
||||||
|
} |
||||||
|
|
||||||
|
@group(0) @binding(0) |
||||||
|
var<storage, read> m: MatrixBatch; |
||||||
|
@group(0) @binding(1) |
||||||
|
var<storage, read_write> result: Matrix; |
||||||
|
|
||||||
|
@compute @workgroup_size(1) |
||||||
|
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { |
||||||
|
result.size = m.size; |
||||||
|
result.size.z = 1; |
||||||
|
|
||||||
|
var vc = result.size.x / 4; |
||||||
|
var resi = global_id.y * vc + global_id.x; |
||||||
|
result.data[resi] = vec4(0f); |
||||||
|
|
||||||
|
var mi = resi; |
||||||
|
for (var z = 0u; z < m.size.z; z++) { |
||||||
|
result.data[resi] += m.data[mi] / vec4(f32(m.size.z)); |
||||||
|
mi += m.size.y * vc; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,18 @@ |
|||||||
|
struct MatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<f32>, |
||||||
|
} |
||||||
|
|
||||||
|
@group(0) @binding(3) |
||||||
|
var<storage, read_write> m: MatrixBatch; |
||||||
|
|
||||||
|
@compute @workgroup_size(1) |
||||||
|
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { |
||||||
|
for (var z = 0u; z < m.size.z; z++) { |
||||||
|
for (var y = 0u; y < m.size.y; y++) { |
||||||
|
for (var x = 0u; x < m.size.x; x++) { |
||||||
|
m.data[z * m.size.y * m.size.x + y * m.size.x + x] = 0f; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,26 @@ |
|||||||
|
alias number = f32; |
||||||
|
|
||||||
|
struct ScalarBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<number>, |
||||||
|
} |
||||||
|
|
||||||
|
struct MatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<vec4<number>>, |
||||||
|
} |
||||||
|
|
||||||
|
@group(0) @binding(0) |
||||||
|
var<storage, read> l: ScalarBatch; |
||||||
|
@group(0) @binding(1) |
||||||
|
var<storage, read> r_or_result: MatrixBatch; |
||||||
|
@group(0) @binding(2) |
||||||
|
var<storage, read_write> result: MatrixBatch; |
||||||
|
|
||||||
|
@compute @workgroup_size(1) |
||||||
|
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { |
||||||
|
result.size = r_or_result.size; |
||||||
|
|
||||||
|
var resi = (global_id.z * result.size.y + global_id.y) * result.size.x / 4 + global_id.x; |
||||||
|
result.data[resi] = l.data[global_id.z] * r_or_result.data[resi]; |
||||||
|
} |
||||||
@ -0,0 +1,21 @@ |
|||||||
|
alias number = f32; |
||||||
|
|
||||||
|
struct MatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<vec4<number>>, |
||||||
|
} |
||||||
|
|
||||||
|
@group(0) @binding(0) |
||||||
|
var<storage, read> l: MatrixBatch; |
||||||
|
@group(0) @binding(1) |
||||||
|
var<storage, read> r_or_result: MatrixBatch; |
||||||
|
@group(0) @binding(2) |
||||||
|
var<storage, read_write> result: MatrixBatch; |
||||||
|
|
||||||
|
@compute @workgroup_size(1) |
||||||
|
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { |
||||||
|
result.size = l.size; |
||||||
|
|
||||||
|
var resi = (global_id.z * result.size.y + global_id.y) * result.size.x / 4 + global_id.x; |
||||||
|
result.data[resi] = l.data[resi] / r_or_result.data[resi]; |
||||||
|
} |
||||||
@ -0,0 +1,21 @@ |
|||||||
|
alias number = f32; |
||||||
|
|
||||||
|
struct MatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<vec4<number>>, |
||||||
|
} |
||||||
|
|
||||||
|
@group(0) @binding(0) |
||||||
|
var<storage, read> l: MatrixBatch; |
||||||
|
@group(0) @binding(1) |
||||||
|
var<storage, read> r_or_result: MatrixBatch; |
||||||
|
@group(0) @binding(2) |
||||||
|
var<storage, read_write> result: MatrixBatch; |
||||||
|
|
||||||
|
@compute @workgroup_size(1) |
||||||
|
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { |
||||||
|
result.size = l.size; |
||||||
|
|
||||||
|
var resi = (global_id.z * result.size.y + global_id.y) * result.size.x / 4 + global_id.x; |
||||||
|
result.data[resi] = l.data[resi] * r_or_result.data[resi]; |
||||||
|
} |
||||||
@ -0,0 +1,26 @@ |
|||||||
|
alias number = f32; |
||||||
|
|
||||||
|
struct Scalar { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<number>, |
||||||
|
} |
||||||
|
|
||||||
|
struct MatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<vec4<number>>, |
||||||
|
} |
||||||
|
|
||||||
|
@group(0) @binding(0) |
||||||
|
var<storage, read> l: Scalar; |
||||||
|
@group(0) @binding(1) |
||||||
|
var<storage, read> r_or_result: MatrixBatch; |
||||||
|
@group(0) @binding(2) |
||||||
|
var<storage, read_write> result: MatrixBatch; |
||||||
|
|
||||||
|
@compute @workgroup_size(1) |
||||||
|
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { |
||||||
|
result.size = r_or_result.size; |
||||||
|
|
||||||
|
var resi = (global_id.z * result.size.y + global_id.y) * result.size.x / 4 + global_id.x; |
||||||
|
result.data[resi] = l.data[0] * r_or_result.data[resi]; |
||||||
|
} |
||||||
@ -0,0 +1,21 @@ |
|||||||
|
alias number = f32; |
||||||
|
|
||||||
|
struct MatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<vec4<number>>, |
||||||
|
} |
||||||
|
|
||||||
|
@group(0) @binding(0) |
||||||
|
var<storage, read> l: MatrixBatch; |
||||||
|
@group(0) @binding(1) |
||||||
|
var<storage, read> r_or_result: MatrixBatch; |
||||||
|
@group(0) @binding(2) |
||||||
|
var<storage, read_write> result: MatrixBatch; |
||||||
|
|
||||||
|
@compute @workgroup_size(1) |
||||||
|
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { |
||||||
|
result.size = l.size; |
||||||
|
|
||||||
|
var resi = (global_id.z * result.size.y + global_id.y) * result.size.x / 4 + global_id.x; |
||||||
|
result.data[resi] = l.data[resi] - r_or_result.data[resi]; |
||||||
|
} |
||||||
@ -0,0 +1,21 @@ |
|||||||
|
alias number = f32; |
||||||
|
|
||||||
|
struct MatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<vec4<number>>, |
||||||
|
} |
||||||
|
|
||||||
|
@group(0) @binding(0) |
||||||
|
var<storage, read> l: MatrixBatch; |
||||||
|
@group(0) @binding(1) |
||||||
|
var<storage, read> r_or_result: MatrixBatch; |
||||||
|
@group(0) @binding(2) |
||||||
|
var<storage, read_write> result: MatrixBatch; |
||||||
|
|
||||||
|
@compute @workgroup_size(1) |
||||||
|
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { |
||||||
|
result.size = l.size; |
||||||
|
|
||||||
|
var resi = (global_id.z * result.size.y + global_id.y) * result.size.x / 4 + global_id.x; |
||||||
|
result.data[resi] = l.data[resi] + r_or_result.data[resi]; |
||||||
|
} |
||||||
@ -0,0 +1,34 @@ |
|||||||
|
alias number = f32; |
||||||
|
|
||||||
|
struct Scalar { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<number>, |
||||||
|
} |
||||||
|
|
||||||
|
struct MatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<number>, |
||||||
|
} |
||||||
|
|
||||||
|
struct UVectorBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<u32>, |
||||||
|
} |
||||||
|
|
||||||
|
@group(0) @binding(0) |
||||||
|
var<storage, read> m: MatrixBatch; |
||||||
|
@group(0) @binding(1) |
||||||
|
var<storage, read> v: UVectorBatch; |
||||||
|
@group(0) @binding(2) |
||||||
|
var<storage, read> lr: Scalar; |
||||||
|
@group(0) @binding(3) |
||||||
|
var<storage, read_write> grs: MatrixBatch; |
||||||
|
|
||||||
|
@compute @workgroup_size(1) |
||||||
|
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { |
||||||
|
grs.size = m.size; |
||||||
|
|
||||||
|
var vi = global_id.z * v.size.x + global_id.y; |
||||||
|
var gi = vi * grs.size.x + global_id.x; |
||||||
|
grs.data[gi] = lr.data[0] * (m.data[gi] - select(0f, 1f, v.data[vi] == global_id.x)); |
||||||
|
} |
||||||
@ -0,0 +1,48 @@ |
|||||||
|
struct Scalar { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<f32>, |
||||||
|
} |
||||||
|
|
||||||
|
struct MatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<f32>, |
||||||
|
} |
||||||
|
|
||||||
|
@group(0) @binding(0) |
||||||
|
var<storage, read> ds: Scalar; |
||||||
|
@group(0) @binding(1) |
||||||
|
var<storage, read> wa: MatrixBatch; |
||||||
|
@group(0) @binding(2) |
||||||
|
var<storage, read> v: MatrixBatch; |
||||||
|
@group(0) @binding(3) |
||||||
|
var<storage, read> otdldo: MatrixBatch; |
||||||
|
@group(0) @binding(4) |
||||||
|
var<storage, read_write> dlds: MatrixBatch; |
||||||
|
|
||||||
|
@compute @workgroup_size(1) |
||||||
|
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { |
||||||
|
dlds.size = wa.size; |
||||||
|
|
||||||
|
var s = 0f; |
||||||
|
var li = (global_id.z * v.size.y + global_id.x) * v.size.x; |
||||||
|
var ri = global_id.z * otdldo.size.x * otdldo.size.y + global_id.y; |
||||||
|
|
||||||
|
for (var i = 0u; i < v.size.x; i++) { |
||||||
|
var cs = 0f; |
||||||
|
var lj = global_id.z * wa.size.y * wa.size.x + global_id.y * wa.size.x; |
||||||
|
var mj = global_id.z * v.size.x * v.size.y + i; |
||||||
|
for (var j = 0u; j < wa.size.x; j++) { |
||||||
|
cs += wa.data[lj] * v.data[mj]; |
||||||
|
lj++; |
||||||
|
mj += v.size.x; |
||||||
|
} |
||||||
|
|
||||||
|
s += (v.data[li] - cs) * otdldo.data[ri]; |
||||||
|
|
||||||
|
li++; |
||||||
|
ri += otdldo.size.x; |
||||||
|
} |
||||||
|
|
||||||
|
let resi = (global_id.z * dlds.size.y + global_id.y) * dlds.size.x + global_id.x; |
||||||
|
dlds.data[resi] = ds.data[0] * wa.data[resi] * s; |
||||||
|
} |
||||||
@ -0,0 +1,43 @@ |
|||||||
|
struct Scalar { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<f32>, |
||||||
|
} |
||||||
|
|
||||||
|
struct Matrix { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<vec4<f32>>, |
||||||
|
} |
||||||
|
|
||||||
|
struct MatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<vec4<f32>>, |
||||||
|
} |
||||||
|
|
||||||
|
@group(0) @binding(0) |
||||||
|
var<storage, read> ebs: MatrixBatch; |
||||||
|
@group(0) @binding(1) |
||||||
|
var<storage, read> ows: Matrix; |
||||||
|
@group(0) @binding(2) |
||||||
|
var<storage, read> tmp: Scalar; |
||||||
|
@group(0) @binding(3) |
||||||
|
var<storage, read_write> dts: MatrixBatch; |
||||||
|
|
||||||
|
@compute @workgroup_size(1) |
||||||
|
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { |
||||||
|
dts.size.x = ows.size.y; |
||||||
|
dts.size.y = ebs.size.y; |
||||||
|
dts.size.z = ebs.size.z; |
||||||
|
|
||||||
|
let vc = ebs.size.x / 4; |
||||||
|
let eo = (global_id.z * ebs.size.y + global_id.y) * vc; |
||||||
|
let oo = 4 * global_id.x * vc; |
||||||
|
var s = vec4(0f); |
||||||
|
for (var i = 0u; i < vc; i++) { |
||||||
|
let eb = ebs.data[eo + i]; |
||||||
|
s.x += dot(eb, ows.data[oo + 0 * vc + i]); |
||||||
|
s.y += dot(eb, ows.data[oo + 1 * vc + i]); |
||||||
|
s.z += dot(eb, ows.data[oo + 2 * vc + i]); |
||||||
|
s.w += dot(eb, ows.data[oo + 3 * vc + i]); |
||||||
|
} |
||||||
|
dts.data[(global_id.z * dts.size.y + global_id.y) * dts.size.x / 4 + global_id.x] = tmp.data[0] * s; |
||||||
|
} |
||||||
@ -0,0 +1,30 @@ |
|||||||
|
struct MatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<f32>, |
||||||
|
} |
||||||
|
|
||||||
|
@group(0) @binding(0) |
||||||
|
var<storage, read> ilogits: MatrixBatch; |
||||||
|
@group(0) @binding(1) |
||||||
|
var<storage, read> inp: MatrixBatch; |
||||||
|
@group(0) @binding(2) |
||||||
|
var<storage, read> pos: MatrixBatch; |
||||||
|
@group(0) @binding(3) |
||||||
|
var<storage, read_write> r: MatrixBatch; |
||||||
|
|
||||||
|
@compute @workgroup_size(1) |
||||||
|
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { |
||||||
|
r.size = pos.size; |
||||||
|
|
||||||
|
var s = 0f; |
||||||
|
var li = (global_id.z * ilogits.size.y + global_id.y) * ilogits.size.x; |
||||||
|
var ri = global_id.z * inp.size.x * inp.size.y + global_id.x; |
||||||
|
for (var i = 0u; i < ilogits.size.x; i++) { |
||||||
|
s += ilogits.data[li] * inp.data[ri]; |
||||||
|
li++; |
||||||
|
ri += inp.size.x; |
||||||
|
} |
||||||
|
|
||||||
|
var resi = (global_id.z * r.size.y + global_id.y) * r.size.x + global_id.x; |
||||||
|
r.data[resi] = s + pos.data[resi]; |
||||||
|
} |
||||||
@ -0,0 +1,47 @@ |
|||||||
|
struct MatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<f32>, |
||||||
|
} |
||||||
|
|
||||||
|
@group(0) @binding(0) |
||||||
|
var<storage, read> ilogits: MatrixBatch; |
||||||
|
@group(0) @binding(0) |
||||||
|
var<storage, read> tilogits: MatrixBatch; |
||||||
|
@group(0) @binding(1) |
||||||
|
var<storage, read> dldsq: MatrixBatch; |
||||||
|
@group(0) @binding(2) |
||||||
|
var<storage, read> dldsk: MatrixBatch; |
||||||
|
@group(0) @binding(3) |
||||||
|
var<storage, read> totdldowa: MatrixBatch; |
||||||
|
@group(0) @binding(4) |
||||||
|
var<storage, read> tqry: MatrixBatch; |
||||||
|
@group(0) @binding(5) |
||||||
|
var<storage, read> tkey: MatrixBatch; |
||||||
|
@group(0) @binding(6) |
||||||
|
var<storage, read> tval: MatrixBatch; |
||||||
|
@group(0) @binding(7) |
||||||
|
var<storage, read_write> ginp: MatrixBatch; |
||||||
|
|
||||||
|
@compute @workgroup_size(1) |
||||||
|
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { |
||||||
|
ginp.size.x = tval.size.x; |
||||||
|
ginp.size.y = ilogits.size.x; |
||||||
|
ginp.size.z = ilogits.size.z; |
||||||
|
|
||||||
|
var ts = 0f; |
||||||
|
var li = global_id.z * ilogits.size.y * ilogits.size.x + global_id.y; |
||||||
|
for (var i = 0u; i < ilogits.size.y; i++) { |
||||||
|
var s = 0f; |
||||||
|
var lj = global_id.z * totdldowa.size.x * totdldowa.size.y + i * totdldowa.size.x; |
||||||
|
var rj = global_id.z * tval.size.x * tval.size.y + global_id.x; |
||||||
|
for (var j = 0u; j < totdldowa.size.x; j++) { |
||||||
|
s += totdldowa.data[lj] * tval.data[rj] + dldsq.data[lj] * tkey.data[rj] + dldsk.data[lj] * tqry.data[rj]; |
||||||
|
lj++; |
||||||
|
rj += tval.size.x; |
||||||
|
} |
||||||
|
ts += ilogits.data[li] * s; |
||||||
|
li += ilogits.size.x; |
||||||
|
} |
||||||
|
|
||||||
|
ginp.data[global_id.z * ginp.size.y * ginp.size.x + global_id.y * ginp.size.x + global_id.x] = ts; |
||||||
|
} |
||||||
@ -0,0 +1,28 @@ |
|||||||
|
struct MatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<f32>, |
||||||
|
} |
||||||
|
|
||||||
|
@group(0) @binding(0) |
||||||
|
var<storage, read> e: MatrixBatch; |
||||||
|
@group(0) @binding(1) |
||||||
|
var<storage, read> dlds: MatrixBatch; |
||||||
|
@group(0) @binding(2) |
||||||
|
var<storage, read_write> gtattw: MatrixBatch; |
||||||
|
|
||||||
|
@compute @workgroup_size(1) |
||||||
|
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { |
||||||
|
gtattw.size.z = e.size.z; |
||||||
|
gtattw.size.y = e.size.x; |
||||||
|
gtattw.size.x = e.size.x; |
||||||
|
|
||||||
|
var s = 0f; |
||||||
|
var li = global_id.z * e.size.x * e.size.y + global_id.x; |
||||||
|
var ri = global_id.z * dlds.size.x * dlds.size.y + global_id.y; |
||||||
|
for (var i = 0u; i < e.size.y; i++) { |
||||||
|
s += e.data[li] * dlds.data[ri]; |
||||||
|
li += e.size.x; |
||||||
|
ri += dlds.size.x; |
||||||
|
} |
||||||
|
gtattw.data[global_id.z * gtattw.size.y * gtattw.size.x + global_id.y * gtattw.size.x + global_id.x] = s; |
||||||
|
} |
||||||
@ -0,0 +1,28 @@ |
|||||||
|
struct MatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<f32>, |
||||||
|
} |
||||||
|
|
||||||
|
@group(0) @binding(0) |
||||||
|
var<storage, read> a: MatrixBatch; |
||||||
|
@group(0) @binding(1) |
||||||
|
var<storage, read> dldo: MatrixBatch; |
||||||
|
@group(0) @binding(2) |
||||||
|
var<storage, read_write> gtout: MatrixBatch; |
||||||
|
|
||||||
|
@compute @workgroup_size(1) |
||||||
|
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { |
||||||
|
gtout.size = a.size; |
||||||
|
gtout.size.y = dldo.size.x; |
||||||
|
|
||||||
|
var s = 0f; |
||||||
|
var ai = global_id.z * a.size.y * a.size.x + global_id.x; |
||||||
|
var di = global_id.z * dldo.size.x * dldo.size.y + global_id.y; |
||||||
|
for (var i = 0u; i < a.size.y; i++) { |
||||||
|
s += a.data[ai] * dldo.data[di]; |
||||||
|
ai += a.size.x; |
||||||
|
di += dldo.size.x; |
||||||
|
} |
||||||
|
|
||||||
|
gtout.data[global_id.z * gtout.size.y * gtout.size.x + global_id.y * gtout.size.x + global_id.x] = s; |
||||||
|
} |
||||||
@ -0,0 +1,19 @@ |
|||||||
|
struct UVectorBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<u32>, |
||||||
|
} |
||||||
|
|
||||||
|
struct MatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<f32>, |
||||||
|
} |
||||||
|
|
||||||
|
@group(0) @binding(0) |
||||||
|
var<storage, read> v: UVectorBatch; |
||||||
|
@group(0) @binding(1) |
||||||
|
var<storage, read_write> result: MatrixBatch; |
||||||
|
|
||||||
|
@compute @workgroup_size(1) |
||||||
|
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { |
||||||
|
result.data[global_id.z * result.size.y * result.size.x + global_id.y * result.size.x + global_id.x] = select(0f, 1f, global_id.x == v.data[global_id.z * v.size.x + global_id.y]); |
||||||
|
} |
||||||
@ -0,0 +1,37 @@ |
|||||||
|
alias number = f32; |
||||||
|
|
||||||
|
struct VectorBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<u32>, |
||||||
|
} |
||||||
|
|
||||||
|
struct MatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<vec4<number>>, |
||||||
|
} |
||||||
|
|
||||||
|
@group(0) @binding(0) |
||||||
|
var<storage, read> v: VectorBatch; |
||||||
|
@group(0) @binding(1) |
||||||
|
var<storage, read> m: MatrixBatch; |
||||||
|
@group(0) @binding(2) |
||||||
|
var<storage, read_write> result: MatrixBatch; |
||||||
|
|
||||||
|
@compute @workgroup_size(1) |
||||||
|
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { |
||||||
|
result.size = m.size; |
||||||
|
|
||||||
|
var vc = result.size.x / 4; |
||||||
|
var resi = (global_id.z * result.size.y + global_id.y) * vc + global_id.x; |
||||||
|
result.data[resi] = vec4(0f); |
||||||
|
|
||||||
|
var mi = global_id.z * m.size.y * vc + global_id.x; |
||||||
|
var vi = global_id.z * v.size.x; |
||||||
|
for (var i = 0u; i < v.size.x; i++) { |
||||||
|
if (global_id.y == v.data[vi]) { |
||||||
|
result.data[resi] += m.data[mi]; |
||||||
|
} |
||||||
|
mi += vc; |
||||||
|
vi++; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,31 @@ |
|||||||
|
alias number = f32; |
||||||
|
|
||||||
|
struct MatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<number>, |
||||||
|
} |
||||||
|
|
||||||
|
@group(0) @binding(0) |
||||||
|
var<storage, read> l: MatrixBatch; |
||||||
|
@group(0) @binding(1) |
||||||
|
var<storage, read> r: MatrixBatch; |
||||||
|
@group(0) @binding(2) |
||||||
|
var<storage, read_write> result: MatrixBatch; |
||||||
|
|
||||||
|
@compute @workgroup_size(1) |
||||||
|
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { |
||||||
|
result.size.x = r.size.x; |
||||||
|
result.size.y = l.size.y; |
||||||
|
result.size.z = l.size.z; |
||||||
|
|
||||||
|
var s = 0f; |
||||||
|
var li = global_id.z * l.size.x * l.size.y + global_id.y * l.size.x; |
||||||
|
var ri = global_id.z * r.size.x * r.size.y + global_id.x; |
||||||
|
for (var i = 0u; i < l.size.x; i++) { |
||||||
|
s += l.data[li] * r.data[ri]; |
||||||
|
li++; |
||||||
|
ri += r.size.x; |
||||||
|
} |
||||||
|
|
||||||
|
result.data[global_id.z * result.size.y * result.size.x + global_id.y * result.size.x + global_id.x] = s; |
||||||
|
} |
||||||
@ -0,0 +1,29 @@ |
|||||||
|
struct MatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<f32>, |
||||||
|
} |
||||||
|
|
||||||
|
@group(0) @binding(0) |
||||||
|
var<storage, read> dldo: MatrixBatch; |
||||||
|
@group(0) @binding(1) |
||||||
|
var<storage, read> tout: MatrixBatch; |
||||||
|
@group(0) @binding(2) |
||||||
|
var<storage, read_write> otdldo: MatrixBatch; |
||||||
|
|
||||||
|
@compute @workgroup_size(1) |
||||||
|
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { |
||||||
|
otdldo.size.x = dldo.size.y; |
||||||
|
otdldo.size.y = tout.size.x; |
||||||
|
otdldo.size.z = dldo.size.z; |
||||||
|
|
||||||
|
var s = 0f; |
||||||
|
var li = (global_id.z * dldo.size.y + global_id.x) * dldo.size.x; |
||||||
|
var ri = global_id.z * tout.size.x * tout.size.y + global_id.y; |
||||||
|
for (var i = 0u; i < dldo.size.x; i++) { |
||||||
|
s += dldo.data[li] * tout.data[ri]; |
||||||
|
li++; |
||||||
|
ri += tout.size.x; |
||||||
|
} |
||||||
|
|
||||||
|
otdldo.data[(global_id.z * otdldo.size.y + global_id.y) * otdldo.size.x + global_id.x] = s; |
||||||
|
} |
||||||
@ -0,0 +1,39 @@ |
|||||||
|
struct MatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<f32>, |
||||||
|
} |
||||||
|
|
||||||
|
struct FVectorBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<f32>, |
||||||
|
} |
||||||
|
|
||||||
|
struct UVectorBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<u32>, |
||||||
|
} |
||||||
|
|
||||||
|
@group(0) @binding(0) |
||||||
|
var<storage, read> m: MatrixBatch; |
||||||
|
@group(0) @binding(1) |
||||||
|
var<storage, read> r: FVectorBatch; |
||||||
|
@group(0) @binding(2) |
||||||
|
var<storage, read_write> result: UVectorBatch; |
||||||
|
|
||||||
|
@compute @workgroup_size(1) |
||||||
|
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { |
||||||
|
result.size.x = m.size.y; |
||||||
|
result.size.z = m.size.z; |
||||||
|
var resi = global_id.z * result.size.x + global_id.x; |
||||||
|
var tp = r.data[global_id.z * r.size.x + global_id.x]; |
||||||
|
var p = 0f; |
||||||
|
var mi = (global_id.z * m.size.y + global_id.x) * m.size.x; |
||||||
|
for (var i = 0u; i < m.size.x; i++) { |
||||||
|
p += m.data[mi]; |
||||||
|
if (p >= tp) { |
||||||
|
result.data[resi] = i; |
||||||
|
break; |
||||||
|
} |
||||||
|
mi++; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,25 @@ |
|||||||
|
struct MatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<vec4<f32>>, |
||||||
|
} |
||||||
|
|
||||||
|
@group(0) @binding(0) |
||||||
|
var<storage, read> b: MatrixBatch; |
||||||
|
@group(0) @binding(1) |
||||||
|
var<storage, read_write> result: MatrixBatch; |
||||||
|
|
||||||
|
@compute @workgroup_size(1) |
||||||
|
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { |
||||||
|
result.size = b.size; |
||||||
|
|
||||||
|
var vc = result.size.x / 4; |
||||||
|
var resi = (global_id.z * result.size.y + global_id.y) * vc + global_id.x; |
||||||
|
var mv = b.data[resi]; |
||||||
|
var bi = (global_id.z * b.size.y + global_id.y) * vc; |
||||||
|
for (var i = 0u; i < vc; i++) { |
||||||
|
mv = max(mv, b.data[bi]); |
||||||
|
bi++; |
||||||
|
} |
||||||
|
var ms = max(max(mv.x, mv.y), max(mv.z, mv.w)); |
||||||
|
result.data[resi] = exp(b.data[resi] - vec4(ms)); |
||||||
|
} |
||||||
@ -0,0 +1,25 @@ |
|||||||
|
struct MatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<vec4<f32>>, |
||||||
|
} |
||||||
|
|
||||||
|
@group(0) @binding(0) |
||||||
|
var<storage, read> b: MatrixBatch; |
||||||
|
@group(0) @binding(1) |
||||||
|
var<storage, read_write> result: MatrixBatch; |
||||||
|
|
||||||
|
@compute @workgroup_size(1) |
||||||
|
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { |
||||||
|
result.size = b.size; |
||||||
|
|
||||||
|
var vc = result.size.x / 4; |
||||||
|
var resi = (global_id.z * result.size.y + global_id.y) * vc + global_id.x; |
||||||
|
var sv = vec4(0f); |
||||||
|
var bi = (global_id.z * b.size.y + global_id.y) * vc; |
||||||
|
for (var i = 0u; i < vc; i++) { |
||||||
|
sv += b.data[bi]; |
||||||
|
bi++; |
||||||
|
} |
||||||
|
var ss = sv.x + sv.y + sv.z + sv.w; |
||||||
|
result.data[resi] = b.data[resi] / vec4(ss); |
||||||
|
} |
||||||
@ -0,0 +1,32 @@ |
|||||||
|
alias number = f32; |
||||||
|
|
||||||
|
struct MatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<vec4<number>>, |
||||||
|
} |
||||||
|
|
||||||
|
struct Matrix { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<vec4<number>>, |
||||||
|
} |
||||||
|
|
||||||
|
@group(0) @binding(0) |
||||||
|
var<storage, read> m: MatrixBatch; |
||||||
|
@group(0) @binding(1) |
||||||
|
var<storage, read_write> result: Matrix; |
||||||
|
|
||||||
|
@compute @workgroup_size(1) |
||||||
|
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { |
||||||
|
result.size = m.size; |
||||||
|
result.size.z = 1; |
||||||
|
|
||||||
|
var vc = result.size.x / 4; |
||||||
|
var resi = global_id.y * vc + global_id.x; |
||||||
|
result.data[resi] = vec4(0f); |
||||||
|
|
||||||
|
var mi = resi; |
||||||
|
for (var z = 0u; z < m.size.z; z++) { |
||||||
|
result.data[resi] += m.data[mi]; |
||||||
|
mi += m.size.y * vc; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,29 @@ |
|||||||
|
struct MatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<f32>, |
||||||
|
} |
||||||
|
|
||||||
|
@group(0) @binding(0) |
||||||
|
var<storage, read> wa: MatrixBatch; |
||||||
|
@group(0) @binding(1) |
||||||
|
var<storage, read> otdldo: MatrixBatch; |
||||||
|
@group(0) @binding(2) |
||||||
|
var<storage, read_write> totdldowa: MatrixBatch; |
||||||
|
|
||||||
|
@compute @workgroup_size(1) |
||||||
|
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { |
||||||
|
totdldowa.size.x = otdldo.size.y; |
||||||
|
totdldowa.size.y = wa.size.x; |
||||||
|
totdldowa.size.z = otdldo.size.z; |
||||||
|
|
||||||
|
var s = 0f; |
||||||
|
var li = global_id.z * otdldo.size.x * otdldo.size.y + global_id.x * otdldo.size.x; |
||||||
|
var ri = global_id.z * wa.size.x * wa.size.y + global_id.y; |
||||||
|
for (var i = 0u; i < otdldo.size.x; i++) { |
||||||
|
s += otdldo.data[li] * wa.data[ri]; |
||||||
|
li++; |
||||||
|
ri += wa.size.x; |
||||||
|
} |
||||||
|
|
||||||
|
totdldowa.data[(global_id.z * totdldowa.size.y + global_id.y) * totdldowa.size.x + global_id.x] = s; |
||||||
|
} |
||||||
@ -0,0 +1,20 @@ |
|||||||
|
alias number = f32; |
||||||
|
|
||||||
|
struct MatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<number>, |
||||||
|
} |
||||||
|
|
||||||
|
@group(0) @binding(0) |
||||||
|
var<storage, read> in: MatrixBatch; |
||||||
|
@group(0) @binding(1) |
||||||
|
var<storage, read_write> out: MatrixBatch; |
||||||
|
|
||||||
|
@compute @workgroup_size(1) |
||||||
|
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { |
||||||
|
out.size.x = in.size.y; |
||||||
|
out.size.y = in.size.x; |
||||||
|
out.size.z = in.size.z; |
||||||
|
var l = global_id.z * out.size.y * out.size.x; |
||||||
|
out.data[l + global_id.y * out.size.x + global_id.x] = in.data[l + global_id.x * in.size.x + global_id.y]; |
||||||
|
} |
||||||
@ -0,0 +1,26 @@ |
|||||||
|
alias number = f32; |
||||||
|
|
||||||
|
struct Matrix { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<vec4<number>>, |
||||||
|
} |
||||||
|
|
||||||
|
struct MatrixBatch { |
||||||
|
size: vec4<u32>, |
||||||
|
data: array<vec4<number>>, |
||||||
|
} |
||||||
|
|
||||||
|
@group(0) @binding(0) |
||||||
|
var<storage, read> m: Matrix; |
||||||
|
@group(0) @binding(1) |
||||||
|
var<storage, read_write> result: MatrixBatch; |
||||||
|
|
||||||
|
@compute @workgroup_size(1) |
||||||
|
fn main(@builtin(global_invocation_id) global_id: vec3<u32>, @builtin(num_workgroups) num_workgroups: vec3<u32>) { |
||||||
|
result.size = m.size; |
||||||
|
result.size.z = num_workgroups.z * 2; |
||||||
|
|
||||||
|
var vc = result.size.x / 4; |
||||||
|
var mi = global_id.y * vc + global_id.x; |
||||||
|
result.data[global_id.z * result.size.y * vc + mi] = m.data[mi]; |
||||||
|
} |
||||||
@ -1,58 +0,0 @@ |
|||||||
import { _, _l, cnst, g, id, n, pipe } from './core'; |
|
||||||
import { iif } from './bool'; |
|
||||||
import { Zero, ge, ifz, pred, succ } from './num'; |
|
||||||
import { x00 } from './byte'; |
|
||||||
import { fromChar as _c, dec as cdec, inc as cinc, eq, ifn } from './char'; |
|
||||||
import { Pair, first, fst, second, snd, uncurry } from './pair'; |
|
||||||
import { cons, head, repeat, set, singleton, tail, update } from './list'; |
|
||||||
import { Empty, get, len, snoc } from './string'; |
|
||||||
|
|
||||||
n('bf'); |
|
||||||
|
|
||||||
const done = g('done', uncurry._(_l(p => cnst._(uncurry._(_l(is => _l(cs => ge._(head._(is))._(len._(cs)))))._(p))))); |
|
||||||
|
|
||||||
const command = g('command', _l(c => uncurry._(_l(p => cnst._(eq._(c)._(uncurry._(pipe._(get)._(head))._(p))))))); |
|
||||||
|
|
||||||
const advance = g('advance', first._(first._(update._(succ)._(Zero)))); |
|
||||||
|
|
||||||
const save = g('save', first._(first._(_l(is => cons._(head._(is))._(is))))); |
|
||||||
|
|
||||||
const restore = g('restore', first._(first._(tail))); |
|
||||||
|
|
||||||
const reset = g('reset', first._(first._(_l(is => cons._(head._(is))._(tail._(tail._(is))))))); |
|
||||||
|
|
||||||
const inc = g('inc', second._(first._(_l(t => second._(update._(cinc)._(fst._(t)))._(t))))); |
|
||||||
|
|
||||||
const dec = g('dec', second._(first._(_l(t => second._(update._(cdec)._(fst._(t)))._(t))))); |
|
||||||
|
|
||||||
const read = g('read', pipe._(pipe._(uncurry._(get))._(fst))._(snd)); |
|
||||||
|
|
||||||
const input = g('input', second._(uncurry._(_l(t => _l(io => Pair._(second._(set._(head._(fst._(io)))._(fst._(t)))._(t))._(first._(tail)._(io))))))); |
|
||||||
|
|
||||||
const output = g('output', second._(uncurry._(_l(t => pipe._(Pair._(t))._(second._(_l(os => snoc._(os)._(uncurry._(get)._(t))))))))); |
|
||||||
|
|
||||||
const next = g('next', second._(first._(first._(succ)))); |
|
||||||
|
|
||||||
const prev = g('prev', second._(first._(first._(pred)))); |
|
||||||
|
|
||||||
const jump = g('jump', _l(d => _l(s => |
|
||||||
iif._(command._(_c('['))._(s))._(jump._(succ._(d))) |
|
||||||
._(iif._(command._(_c(']'))._(s))._(ifz._(pred._(d))._(cnst._(s))._(jump._(pred._(d)))) |
|
||||||
._(jump._(d)))._(advance._(s))))); |
|
||||||
|
|
||||||
const whlnz = g('whlnz', _l(s => ifn._(read._(s))._(jump._(Zero))._(save)._(s))); |
|
||||||
|
|
||||||
const endwhl = g('endwhl', _l(s => ifn._(read._(s))._(reset)._(pipe._(save)._(restore))._(s))); |
|
||||||
|
|
||||||
const exec = g('exec', _l(s => iif._(done._(s))._(s)._(exec._(advance |
|
||||||
._(iif._(command._(_c('>'))._(s))._(next) |
|
||||||
._(iif._(command._(_c('<'))._(s))._(prev) |
|
||||||
._(iif._(command._(_c('+'))._(s))._(inc) |
|
||||||
._(iif._(command._(_c('-'))._(s))._(dec) |
|
||||||
._(iif._(command._(_c('.'))._(s))._(output) |
|
||||||
._(iif._(command._(_c(','))._(s))._(input) |
|
||||||
._(iif._(command._(_c('['))._(s))._(whlnz) |
|
||||||
._(iif._(command._(_c(']'))._(s))._(endwhl) |
|
||||||
._(id))))))))._(s)))))); |
|
||||||
|
|
||||||
export const run = g('run', _l(p => _l(i => snd._(snd._(snd._(exec._(Pair._(Pair._(singleton._(Zero))._(p))._(Pair._(Pair._(Zero)._(repeat._(x00)))._(Pair._(i)._(Empty)))))))))); |
|
||||||
@ -1,7 +0,0 @@ |
|||||||
import { _l, g, n } from './core'; |
|
||||||
import { iif } from './bool'; |
|
||||||
import { fromString as _s } from './string'; |
|
||||||
|
|
||||||
n('bool.show'); |
|
||||||
|
|
||||||
export const show = g('show', _l(b => iif._(b)._(_s('True'))._(_s('False')))); |
|
||||||
@ -1,34 +0,0 @@ |
|||||||
import { $, Term, _, _l, g, id, n, pipe } from './core'; |
|
||||||
import { EQ, GT, LT } from './ord'; |
|
||||||
|
|
||||||
n('bool'); |
|
||||||
|
|
||||||
export const True = g('True', _l(t => _l(_ => t))); |
|
||||||
|
|
||||||
export const False = g('False', _l(_ => _l(f => f))); |
|
||||||
|
|
||||||
export const iif = g('iif', id); |
|
||||||
|
|
||||||
export const not = g('not', _l(b => b._(False)._(True))); |
|
||||||
|
|
||||||
export const cmp = g('cmp', _l(l => _l(r => l._(r._(EQ)._(GT))._(r._(LT)._(EQ))))); |
|
||||||
|
|
||||||
export const lt = g('lt', _l(l => l._(False))); |
|
||||||
|
|
||||||
export const le = g('le', _l(l => not._(l)._(True))); |
|
||||||
|
|
||||||
export const eq = g('eq', _l(l => _l(r => l._(r)._(not._(r))))); |
|
||||||
|
|
||||||
export const ge = g('ge', _l(l => pipe._(not)._(lt._(l)))); |
|
||||||
|
|
||||||
export const gt = g('gt', _l(l => pipe._(not)._(le._(l)))); |
|
||||||
|
|
||||||
export const and = g('and', _l(l => not._(l)._(False))); |
|
||||||
|
|
||||||
export const or = g('or', _l(l => l._(True))); |
|
||||||
|
|
||||||
export const xor = g('xor', _l(l => _l(r => l._(not._(r))._(r)))); |
|
||||||
|
|
||||||
export const fromBoolean: (b: boolean) => Term = b => b ? True : False; |
|
||||||
|
|
||||||
export const toBoolean: (b: Term) => boolean = b => $(b._(_(true))._(_(false)).reduce()); |
|
||||||
@ -1,7 +0,0 @@ |
|||||||
import { _l, g, n } from './core'; |
|
||||||
import { snd } from './pair'; |
|
||||||
import { show as nshow } from './num.show'; |
|
||||||
|
|
||||||
n('byte.show'); |
|
||||||
|
|
||||||
export const show = g('show', _l(n => nshow._(snd._(n)))); |
|
||||||
@ -1,50 +0,0 @@ |
|||||||
import { Term, _, _l, cnst, g, n } from './core'; |
|
||||||
import { iif } from './bool'; |
|
||||||
import { toNumber as $n, Zero, fromNumber as _n, cmp as ncmp, eq as neq, even as neven, ge as nge, gt as ngt, le as nle, lt as nlt, odd as nodd, pred, succ } from './num'; |
|
||||||
import { Pair, snd, uncurry } from './pair'; |
|
||||||
|
|
||||||
n('byte'); |
|
||||||
|
|
||||||
export const x00 = g('x00', Pair._(_n(255))._(Zero)); |
|
||||||
|
|
||||||
export const xFF = g('xFF', Pair._(Zero)._(_n(255))); |
|
||||||
|
|
||||||
export const Inc = g('Inc', uncurry._(_l(i => _l(s => i._(x00)._(_l(pi => Pair._(pi)._(succ._(s)))))))); |
|
||||||
|
|
||||||
export const ifz = g('ifz', _l(n => _l(t => _l(f => snd._(n)._(t)._(cnst._(f)))))); |
|
||||||
|
|
||||||
export const inc = g('inc', Inc); |
|
||||||
|
|
||||||
export const dec = g('dec', _l(n => uncurry._(_l(i => _l(s => s._(xFF)._(_l(ps => Pair._(succ._(i))._(ps))))))._(n))); |
|
||||||
|
|
||||||
export const cmp = g('cmp', _l(l => _l(r => ncmp._(snd._(l))._(snd._(r))))); |
|
||||||
|
|
||||||
export const lt = g('lt', _l(l => _l(r => nlt._(snd._(l))._(snd._(r))))); |
|
||||||
|
|
||||||
export const le = g('le', _l(l => _l(r => nle._(snd._(l))._(snd._(r))))); |
|
||||||
|
|
||||||
export const eq = g('eq', _l(l => _l(r => neq._(snd._(l))._(snd._(r))))); |
|
||||||
|
|
||||||
export const ge = g('ge', _l(l => _l(r => nge._(snd._(l))._(snd._(r))))); |
|
||||||
|
|
||||||
export const gt = g('gt', _l(l => _l(r => ngt._(snd._(l))._(snd._(r))))); |
|
||||||
|
|
||||||
export const even = g('even', _l(l => _l(r => neven._(snd._(l))._(snd._(r))))); |
|
||||||
|
|
||||||
export const odd = g('odd', _l(l => _l(r => nodd._(snd._(l))._(snd._(r))))); |
|
||||||
|
|
||||||
export const sum = g('sum', _l(l => _l(r => snd._(r)._(l)._(_l(_ => sum._(inc._(l))._(dec._(r))))))); |
|
||||||
|
|
||||||
export const sub = g('sub', _l(l => _l(r => snd._(r)._(l)._(_l(_ => sub._(dec._(l))._(dec._(r))))))); |
|
||||||
|
|
||||||
export const mul = g('mul', _l(l => _l(r => snd._(l)._(x00)._(_l(_ => sum._(r)._(mul._(dec._(l))._(r))))))); |
|
||||||
|
|
||||||
export const div = g('div', _l(l => _l(r => iif._(lt._(l)._(r))._(x00)._(inc._(div._(sub._(l)._(r))._(r)))))); |
|
||||||
|
|
||||||
export const mod = g('mod', _l(l => _l(r => iif._(lt._(l)._(r))._(l)._(mod._(sub._(l)._(r))._(r))))); |
|
||||||
|
|
||||||
export const byte = g('byte', _l(n => n._(x00)._(_l(pn => inc._(byte._(pn)))))); |
|
||||||
|
|
||||||
export const fromNumber: (n: number) => Term = n => !n ? x00 : Inc._(_(() => fromNumber(n - 1))); |
|
||||||
|
|
||||||
export const toNumber: (n: Term) => number = n => $n(snd._(n)); |
|
||||||
@ -1,7 +0,0 @@ |
|||||||
import { _l, g, n, pipe } from './core'; |
|
||||||
import { singleton } from './list'; |
|
||||||
import { fromString as _s, wrap } from './string'; |
|
||||||
|
|
||||||
n('char.show'); |
|
||||||
|
|
||||||
export const show = g('show', _l(c => pipe._(wrap._(_s('\'')))._(singleton)._(c))); |
|
||||||
@ -1,12 +0,0 @@ |
|||||||
import { Term, g, n } from './core'; |
|
||||||
import { toNumber as $n, fromNumber as _n, x00 } from './byte'; |
|
||||||
|
|
||||||
n('char'); |
|
||||||
|
|
||||||
export const Null = g('Null', x00); |
|
||||||
|
|
||||||
export { inc, dec, ifz as ifn, cmp, lt, le, eq, ge, gt, byte as char } from './byte'; |
|
||||||
|
|
||||||
export const fromChar: (c: string) => Term = c => _n(c.charCodeAt(0)); |
|
||||||
|
|
||||||
export const toChar: (c: Term) => string = c => String.fromCharCode($n(c)); |
|
||||||
@ -1,314 +0,0 @@ |
|||||||
import { withProfile as simpleWithProfile, withLog } from './debug'; |
|
||||||
|
|
||||||
const withProfile = (term: Term, action: string, func: () => Term) => simpleWithProfile( |
|
||||||
`${term.expression()}${term.cache ? ' [cached]' : ''}`, |
|
||||||
{ |
|
||||||
cat: action, |
|
||||||
id: term.id, |
|
||||||
bindings: Object.fromEntries(Object.entries(term.bindings).map(([k, v]) => [k, v.toString()])), |
|
||||||
}, |
|
||||||
func, |
|
||||||
); |
|
||||||
|
|
||||||
let index = 0; |
|
||||||
|
|
||||||
export abstract class Term { |
|
||||||
public id = index++; |
|
||||||
public cache: Term | null = null; |
|
||||||
|
|
||||||
public constructor() { } |
|
||||||
|
|
||||||
public refs(_name: string): boolean { |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
public bound(_name: string): boolean { |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
public get bindings(): Record<string, Term> { |
|
||||||
return {}; |
|
||||||
} |
|
||||||
|
|
||||||
public bind(_name: string, _term: Term): Term { |
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
public reduce(): Term { |
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
public context(): string { |
|
||||||
return `#${this.id}${this.cache ? '<cached>' : ''}${!Object.keys(this.bindings).length ? '' : ` [${Object.keys(this.bindings).join(', ')}]`}`; |
|
||||||
} |
|
||||||
|
|
||||||
public expression(): string { |
|
||||||
return this.constructor.name; |
|
||||||
} |
|
||||||
|
|
||||||
public stringify(): string { |
|
||||||
return `${this.context()} ${this.expression()}`; |
|
||||||
} |
|
||||||
|
|
||||||
public _(arg: Term): Term { |
|
||||||
return new Call(this, arg); |
|
||||||
} |
|
||||||
|
|
||||||
public toString(): string { |
|
||||||
return this.expression(); |
|
||||||
} |
|
||||||
|
|
||||||
public toJSON(): string { |
|
||||||
return `<${this.expression()}>`; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
export class Undef extends Term { |
|
||||||
constructor() { super(); } |
|
||||||
|
|
||||||
override reduce(): Term { |
|
||||||
throw new Error('undef'); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
export class Native extends Term { |
|
||||||
public constructor( |
|
||||||
public value: any, |
|
||||||
) { super(); } |
|
||||||
|
|
||||||
public override expression(): string { |
|
||||||
return `\`${JSON.stringify(this.value)}\``; |
|
||||||
} |
|
||||||
|
|
||||||
public override stringify(): string { |
|
||||||
return this.expression(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
export class Deferred extends Term { |
|
||||||
public constructor( |
|
||||||
protected func: (bindings: Record<string, Term>) => Term, |
|
||||||
protected _bindings: Record<string, Term> = {}, |
|
||||||
) { super(); } |
|
||||||
|
|
||||||
public override refs(_name: string): boolean { |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
public override bound(name: string): boolean { |
|
||||||
return !!this._bindings[name]; |
|
||||||
} |
|
||||||
|
|
||||||
public override get bindings(): Record<string, Term> { |
|
||||||
return this._bindings; |
|
||||||
} |
|
||||||
|
|
||||||
public override bind(name: string, term: Term): Term { |
|
||||||
return this.bound(name) || !this.refs(name) |
|
||||||
? this |
|
||||||
: new Deferred(this.func, { ...this.bindings, [name]: term }); |
|
||||||
} |
|
||||||
|
|
||||||
public override reduce(): Term { |
|
||||||
return this.cache = withLog( |
|
||||||
`Executing ${this.stringify()}`, |
|
||||||
() => withProfile( |
|
||||||
this, |
|
||||||
'deferred', |
|
||||||
() => this.cache ?? Object.entries(this.bindings).reduce((t, b) => t.bind(...b), this.func(this.bindings)).reduce(), |
|
||||||
), |
|
||||||
r => `got ${r.stringify()}`, |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
public override expression(): string { |
|
||||||
return '`deferred`'; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
export class Ref extends Term { |
|
||||||
public constructor( |
|
||||||
protected namespace: string, |
|
||||||
protected name: string, |
|
||||||
protected binding: Term | null = null, |
|
||||||
) { super(); } |
|
||||||
|
|
||||||
public override refs(name: string): boolean { |
|
||||||
return this.name === name; |
|
||||||
} |
|
||||||
|
|
||||||
public override bound(name: string): boolean { |
|
||||||
return this.name === name && !!this.binding; |
|
||||||
} |
|
||||||
|
|
||||||
public override get bindings(): Record<string, Term> { |
|
||||||
return this.binding ? { [this.name]: this.binding } : {}; |
|
||||||
} |
|
||||||
|
|
||||||
public override bind(name: string, term: Term): Term { |
|
||||||
return this.bound(name) || !this.refs(name) |
|
||||||
? this |
|
||||||
: new Ref(this.namespace, this.name, term); |
|
||||||
} |
|
||||||
|
|
||||||
public override reduce(): Term { |
|
||||||
return this.cache = withLog( |
|
||||||
`Substituting ${this.stringify()}`, |
|
||||||
() => withProfile( |
|
||||||
this, |
|
||||||
'deref', |
|
||||||
() => this.cache ?? ((this.namespace === 'local' ? this.binding : globals[`${this.namespace}::${this.name}`]) ?? new Undef()).reduce(), |
|
||||||
), |
|
||||||
r => `with ${r.stringify()}`, |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
public override expression(): string { |
|
||||||
return this.name; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
export class Lambda extends Term { |
|
||||||
public constructor(resolve: (p: Ref) => Term); |
|
||||||
public constructor(param: string, body: Term); |
|
||||||
public constructor( |
|
||||||
resolveOrParam: ((p: Ref) => Term) | string, |
|
||||||
maybeBody?: Term, |
|
||||||
) { |
|
||||||
super(); |
|
||||||
this._resolve = typeof resolveOrParam === 'function' ? resolveOrParam : () => new Undef(); |
|
||||||
this._param = typeof resolveOrParam === 'string' ? resolveOrParam : null; |
|
||||||
this._body = maybeBody ?? null; |
|
||||||
} |
|
||||||
|
|
||||||
private _resolve: (p: Ref) => Term; |
|
||||||
public get resolve(): (p: Ref) => Term { |
|
||||||
return this._resolve; |
|
||||||
} |
|
||||||
|
|
||||||
private _param: string | null = null; |
|
||||||
public get param(): string { |
|
||||||
return this._param ??= this.resolve.toString().split(' ')[0]; |
|
||||||
} |
|
||||||
|
|
||||||
private _body: Term | null = null; |
|
||||||
public get body(): Term { |
|
||||||
return this._body ??= this.resolve(new Ref('local', this.param)); |
|
||||||
} |
|
||||||
|
|
||||||
public override refs(name: string): boolean { |
|
||||||
return this.param !== name && this.body.refs(name); |
|
||||||
} |
|
||||||
|
|
||||||
public override bound(name: string): boolean { |
|
||||||
return this.param !== name && this.body.bound(name); |
|
||||||
} |
|
||||||
|
|
||||||
public override get bindings(): Record<string, Term> { |
|
||||||
return this.body.bindings; |
|
||||||
} |
|
||||||
|
|
||||||
public override bind(name: string, term: Term): Term { |
|
||||||
return this.bound(name) || !this.refs(name) |
|
||||||
? this |
|
||||||
: new Lambda(this.param, this.body.bind(name, term)); |
|
||||||
} |
|
||||||
|
|
||||||
public override expression(): string { |
|
||||||
return this.body instanceof Lambda |
|
||||||
? `\\ ${this.param} ${this.body.expression().slice(2)}` |
|
||||||
: `\\ ${this.param} . ${this.body.expression()}`; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
export class Call extends Term { |
|
||||||
public constructor( |
|
||||||
public func: Term, |
|
||||||
public arg: Term, |
|
||||||
) { super(); } |
|
||||||
|
|
||||||
public override refs(name: string): boolean { |
|
||||||
return this.func.refs(name) || this.arg.refs(name); |
|
||||||
} |
|
||||||
|
|
||||||
public override bound(name: string): boolean { |
|
||||||
return this.func.bound(name) || this.arg.bound(name); |
|
||||||
} |
|
||||||
|
|
||||||
override get bindings(): Record<string, Term> { |
|
||||||
return { ...this.func.bindings, ...this.arg.bindings }; |
|
||||||
} |
|
||||||
|
|
||||||
public override bind(name: string, term: Term): Term { |
|
||||||
return this.bound(name) || !this.refs(name) |
|
||||||
? this |
|
||||||
: new Call(this.func.bind(name, term), this.arg.bind(name, term)); |
|
||||||
} |
|
||||||
|
|
||||||
public override reduce(): Term { |
|
||||||
return this.cache = withLog( |
|
||||||
`Reducing ${this.stringify()}`, |
|
||||||
() => withProfile( |
|
||||||
this, |
|
||||||
'call', |
|
||||||
() => { |
|
||||||
if (this.cache) { |
|
||||||
return this.cache; |
|
||||||
} |
|
||||||
const func = this.func.reduce(); |
|
||||||
if (!(func instanceof Lambda)) { |
|
||||||
return new Undef(); |
|
||||||
} |
|
||||||
return withLog( |
|
||||||
`Binding ${this.arg.stringify()} to ${func.param}`, |
|
||||||
() => simpleWithProfile( |
|
||||||
`${func.param} = ${this.arg}`, |
|
||||||
{ cat: 'binding' }, |
|
||||||
() => func.body.bind(func.param, this.arg), |
|
||||||
), |
|
||||||
r => `got ${r.stringify()}`, |
|
||||||
).reduce(); |
|
||||||
}, |
|
||||||
), |
|
||||||
r => `to ${r.stringify()}`, |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
public override expression(): string { |
|
||||||
return `${this.func instanceof Lambda ? `(${this.func.expression()})` : this.func.expression()} ${this.arg instanceof Lambda || this.arg instanceof Call ? `(${this.arg.expression()})` : this.arg.expression()}`; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
const globals: Record<string, Term> = {}; |
|
||||||
|
|
||||||
let namespace: string = 'core'; |
|
||||||
|
|
||||||
export const n = (name: string) => namespace = name; |
|
||||||
|
|
||||||
export const g = (name: string, term: Term) => { |
|
||||||
const full = `${namespace}::${name}`; |
|
||||||
if (globals[full]) { |
|
||||||
throw new Error(`Duplicate global: "${full}"`); |
|
||||||
} |
|
||||||
globals[full] = term; |
|
||||||
return new Ref(namespace, name); |
|
||||||
}; |
|
||||||
|
|
||||||
export const _l = (f: (p: Ref) => Term) => new Lambda(f); |
|
||||||
|
|
||||||
type Deferedify = { |
|
||||||
(value: (bindings: Record<string, Term>) => Term): Deferred |
|
||||||
(value: boolean | number | string | object): Native |
|
||||||
}; |
|
||||||
|
|
||||||
export const _: Deferedify = ((value: any) => typeof value === 'function' ? new Deferred(value) : new Native(value)) as Deferedify; |
|
||||||
export const $ = <T>(t: Term): T => { |
|
||||||
const rt = t.reduce(); |
|
||||||
return rt instanceof Native ? rt.value : rt.stringify(); |
|
||||||
}; |
|
||||||
|
|
||||||
export const undef = g('undef', new Undef()); |
|
||||||
|
|
||||||
export const id = g('id', _l(f => f)); |
|
||||||
export const cnst = g('cnst', _l(x => _l(_ => x))); |
|
||||||
export const pipe = g('pipe', _l(f => _l(g => _l(x => f._(g._(x)))))); |
|
||||||
@ -0,0 +1,301 @@ |
|||||||
|
import fs from 'node:fs'; |
||||||
|
import type { Scalar } from './scalar'; |
||||||
|
|
||||||
|
interface ComputePipeline { |
||||||
|
pipeline: GPUComputePipeline; |
||||||
|
layout: GPUBindGroupLayout; |
||||||
|
} |
||||||
|
|
||||||
|
const PIPELINES: Map<GPUDevice, Map<string, ComputePipeline>> = new Map(); |
||||||
|
|
||||||
|
const COMPWISE_SHADER = { |
||||||
|
sum: fs.readFileSync('shader/csum.wgsl').toString(), |
||||||
|
sub: fs.readFileSync('shader/csub.wgsl').toString(), |
||||||
|
mul: fs.readFileSync('shader/cmul.wgsl').toString(), |
||||||
|
div: fs.readFileSync('shader/cdiv.wgsl').toString(), |
||||||
|
scale: fs.readFileSync('shader/cscale.wgsl').toString(), |
||||||
|
}; |
||||||
|
|
||||||
|
export function createComputePipeline( |
||||||
|
device: GPUDevice, |
||||||
|
name: string, |
||||||
|
shader: () => string, |
||||||
|
entry: string = 'main', |
||||||
|
) { |
||||||
|
if (!PIPELINES.has(device)) { |
||||||
|
PIPELINES.set(device, new Map()); |
||||||
|
} |
||||||
|
|
||||||
|
const d = PIPELINES.get(device)!; |
||||||
|
if (!d.has(name)) { |
||||||
|
const pipeline = device.createComputePipeline({ |
||||||
|
label: name, |
||||||
|
layout: 'auto', |
||||||
|
compute: { |
||||||
|
module: device.createShaderModule({ code: shader() }), |
||||||
|
entryPoint: entry, |
||||||
|
}, |
||||||
|
}); |
||||||
|
const layout = pipeline.getBindGroupLayout(0); |
||||||
|
|
||||||
|
d.set(name, { pipeline, layout }); |
||||||
|
} |
||||||
|
|
||||||
|
return d.get(name)!; |
||||||
|
} |
||||||
|
|
||||||
|
export abstract class GPUStruct<A extends Uint32Array | Int32Array | Float32Array> { |
||||||
|
public static dunit = 8; |
||||||
|
public static lunit = 2; |
||||||
|
|
||||||
|
protected readonly _label: string; |
||||||
|
protected readonly _device: GPUDevice; |
||||||
|
protected readonly _array: new (size: number | ArrayBufferLike | ArrayLike<number>) => A; |
||||||
|
protected readonly _inbuffer: GPUBuffer; |
||||||
|
protected readonly _dummybuffer: GPUBuffer; |
||||||
|
protected readonly _outbuffer: GPUBuffer; |
||||||
|
protected _op: 'reading' | 'writing' | null = null; |
||||||
|
protected _dirty: 'front' | 'back' | null = 'front'; |
||||||
|
|
||||||
|
protected abstract workgroups(pm?: number): [number, number, number]; |
||||||
|
protected abstract workgroup(): [number, number, number]; |
||||||
|
|
||||||
|
public readonly size: Int32Array; |
||||||
|
public readonly data: A; |
||||||
|
|
||||||
|
public get type(): 'u32' | 'i32' | 'f32' { |
||||||
|
switch (this._array as unknown) { |
||||||
|
case Uint32Array: return 'u32'; |
||||||
|
case Int32Array: return 'i32'; |
||||||
|
case Float32Array: return 'f32'; |
||||||
|
default: throw new Error('Unknown array type.'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public get length(): number { |
||||||
|
return this.data.length; |
||||||
|
} |
||||||
|
|
||||||
|
public get byteLength(): number { |
||||||
|
return this.size.byteLength + this.data.byteLength; |
||||||
|
} |
||||||
|
|
||||||
|
public constructor( |
||||||
|
device: GPUDevice, |
||||||
|
array: new (size: number) => A, |
||||||
|
label: string, |
||||||
|
size: [number, number, number, number], |
||||||
|
) { |
||||||
|
this._device = device; |
||||||
|
this._array = array; |
||||||
|
this._label = label ?? 'struct'; |
||||||
|
this.size = new Int32Array(size); |
||||||
|
this.data = new array(size[0] * size[1] * size[2] * size[3]); |
||||||
|
|
||||||
|
this._inbuffer = device.createBuffer({ |
||||||
|
label: `${this._label}in`, |
||||||
|
size: Math.max(32, this.byteLength), |
||||||
|
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST, |
||||||
|
}); |
||||||
|
|
||||||
|
this._dummybuffer = device.createBuffer({ |
||||||
|
label: `${label}dummy`, |
||||||
|
size: 0, |
||||||
|
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST, |
||||||
|
}); |
||||||
|
|
||||||
|
this._outbuffer = this._device.createBuffer({ |
||||||
|
label: `${label}out`, |
||||||
|
size: Math.max(32, this.byteLength), |
||||||
|
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
public write(): void { |
||||||
|
if (!this._dirty) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (this._op) { |
||||||
|
throw new Error('Sync in progress.'); |
||||||
|
} |
||||||
|
|
||||||
|
this._op = 'writing'; |
||||||
|
this._device.queue.writeBuffer(this._inbuffer, 0, new Uint8Array(this.size.buffer)); |
||||||
|
this._device.queue.writeBuffer(this._inbuffer, this.size.byteLength, new Uint8Array(this.data.buffer)); |
||||||
|
this._dirty = null; |
||||||
|
this._op = null; |
||||||
|
} |
||||||
|
|
||||||
|
public async read(): Promise<void> { |
||||||
|
if (!this._dirty) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (this._op) { |
||||||
|
throw new Error('Sync in progress.'); |
||||||
|
} |
||||||
|
|
||||||
|
this._op = 'reading'; |
||||||
|
|
||||||
|
const copy = this._device.createCommandEncoder(); |
||||||
|
copy.copyBufferToBuffer(this._inbuffer, 0, this._outbuffer, 0, this.byteLength); |
||||||
|
this._device.queue.submit([copy.finish()]); |
||||||
|
|
||||||
|
await this._device.queue.onSubmittedWorkDone(); |
||||||
|
|
||||||
|
await this._outbuffer.mapAsync(GPUMapMode.READ); |
||||||
|
this.data.set(new this._array(this._outbuffer.getMappedRange()).slice(this.size.length)); |
||||||
|
this._outbuffer.unmap(); |
||||||
|
this._dirty = null; |
||||||
|
this._op = null; |
||||||
|
} |
||||||
|
|
||||||
|
public buffer(): GPUBuffer { |
||||||
|
if (this._dirty === 'front') { |
||||||
|
this.write(); |
||||||
|
} |
||||||
|
return this._inbuffer; |
||||||
|
} |
||||||
|
|
||||||
|
protected workgroupify(shader: string): string { |
||||||
|
const [x, y, z] = this.workgroup(); |
||||||
|
if (!x || !y || !z) { |
||||||
|
throw new Error(`Invalid workgroup size: (${x}, ${y}, ${z}).`); |
||||||
|
} |
||||||
|
return shader.replace(/@workgroup_size\(.*?\)/g, `@workgroup_size(${x}, ${y}, ${z})`); |
||||||
|
} |
||||||
|
|
||||||
|
protected inplacify(shader: string): string { |
||||||
|
let r = ''; |
||||||
|
return shader.replace(/\w+_or_result/g, v => r = r ? 'result' : v); // The first occurrence must be the declaration.
|
||||||
|
} |
||||||
|
|
||||||
|
protected typify(shader: string): string { |
||||||
|
return shader.replace(/alias number = .+?;/g, `alias number = ${this.type};`); |
||||||
|
} |
||||||
|
|
||||||
|
public calculate( |
||||||
|
name: string, |
||||||
|
shader: string, |
||||||
|
args: GPUStruct<Uint32Array | Int32Array | Float32Array>[], |
||||||
|
ip = false, |
||||||
|
pm = 1, |
||||||
|
): typeof this { |
||||||
|
const encoder = this._device.createCommandEncoder(); |
||||||
|
const pass = encoder.beginComputePass(); |
||||||
|
const { pipeline, layout } = createComputePipeline(this._device, `${name}${ip ? '_inplace_' : '_'}${this.type}`, () => this.workgroupify(this.typify(ip ? this.inplacify(shader) : shader))); |
||||||
|
|
||||||
|
pass.setPipeline(pipeline); |
||||||
|
pass.setBindGroup(0, this._device.createBindGroup({ |
||||||
|
layout, entries: [ |
||||||
|
...args.map((s, i) => ({ binding: i, resource: { buffer: s.buffer() } })), |
||||||
|
{ binding: args.length + +ip, resource: { buffer: this.buffer() } }, |
||||||
|
], |
||||||
|
})); |
||||||
|
|
||||||
|
const [x, y, z] = this.workgroups(pm); |
||||||
|
if (!x || !y || !z || Math.floor(x) !== x || Math.floor(y) !== y || Math.floor(z) !== z) { |
||||||
|
throw new Error(`Invalid workgroup count: (${x}, ${y}, ${z}).`); |
||||||
|
} |
||||||
|
pass.dispatchWorkgroups(x, y, z); |
||||||
|
pass.end(); |
||||||
|
|
||||||
|
this._device.queue.submit([encoder.finish()]); |
||||||
|
|
||||||
|
this._dirty = 'back'; |
||||||
|
|
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
protected static assertUnique(...ms: GPUStruct<Uint32Array | Int32Array | Float32Array>[]): void { |
||||||
|
const map: Map<GPUStruct<Uint32Array | Int32Array | Float32Array>, boolean> = new Map(); |
||||||
|
for (const m of ms) { |
||||||
|
if (map.has(m)) { |
||||||
|
throw new Error('Can not use the same GPU structure multiple times.'); |
||||||
|
} |
||||||
|
map.set(m, true); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected static assertColocated(...ms: GPUStruct<Uint32Array | Int32Array | Float32Array>[]): void { |
||||||
|
const map: Map<GPUDevice, boolean> = new Map(); |
||||||
|
for (const m of ms) { |
||||||
|
map.set(m._device, true); |
||||||
|
if (map.size > 1) { |
||||||
|
throw new Error('Can not use GPU structures stored on multiple devices.'); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected static assertSimilar(...ms: GPUStruct<Uint32Array | Int32Array | Float32Array>[]): void { |
||||||
|
for (const m of ms) { |
||||||
|
if (m.length !== ms[0].length |
||||||
|
|| m.workgroup()[0] !== ms[0].workgroup()[0] |
||||||
|
|| m.workgroup()[1] !== ms[0].workgroup()[1] |
||||||
|
|| m.workgroup()[2] !== ms[0].workgroup()[2]) { |
||||||
|
throw new Error('Can not use GPU structures with different dimensions.'); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected static assertPackable(pm: number, ...ms: GPUStruct<Uint32Array | Int32Array | Float32Array>[]): void { |
||||||
|
for (const m of ms) { |
||||||
|
if (m.workgroups(pm).some(v => v < 1)) { |
||||||
|
throw new Error(`Struct ${m._label} size must be divisible by ${pm}.`); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static compwise< |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
S extends GPUStruct<A>, |
||||||
|
>(op: keyof typeof COMPWISE_SHADER, l: S, r: S, result: S): S { |
||||||
|
this.assertUnique(l, r); |
||||||
|
this.assertUnique(l, result); |
||||||
|
this.assertColocated(l, r, result); |
||||||
|
this.assertPackable(4, l, r, result); |
||||||
|
|
||||||
|
return result.calculate(op, COMPWISE_SHADER[op], result === r ? [l] : [l, r], result === r, 4); |
||||||
|
} |
||||||
|
|
||||||
|
public static csum< |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
S extends GPUStruct<A>, |
||||||
|
>(l: S, r: S, result: S): S { |
||||||
|
return GPUStruct.compwise('sum', l, r, result); |
||||||
|
} |
||||||
|
|
||||||
|
public static csub< |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
S extends GPUStruct<A>, |
||||||
|
>(l: S, r: S, result: S): S { |
||||||
|
return GPUStruct.compwise('sub', l, r, result); |
||||||
|
} |
||||||
|
|
||||||
|
public static cmul< |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
S extends GPUStruct<A>, |
||||||
|
>(l: S, r: S, result: S): S { |
||||||
|
return GPUStruct.compwise('mul', l, r, result); |
||||||
|
} |
||||||
|
|
||||||
|
public static cdiv< |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
S extends GPUStruct<A>, |
||||||
|
>(l: S, r: S, result: S): S { |
||||||
|
return GPUStruct.compwise('div', l, r, result); |
||||||
|
} |
||||||
|
|
||||||
|
public static cscale< |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
S extends GPUStruct<A>, |
||||||
|
>(l: Scalar<A>, r: S, result: S): S { |
||||||
|
this.assertUnique(l, r); |
||||||
|
this.assertUnique(l, result); |
||||||
|
this.assertColocated(l, r, result); |
||||||
|
this.assertPackable(4, r, result); |
||||||
|
|
||||||
|
return result.calculate('scale', COMPWISE_SHADER.scale, result === r ? [l] : [l, r], result === r, 4); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,289 @@ |
|||||||
|
import fs from 'node:fs'; |
||||||
|
import { Matrix as CPUMatrix, Vector as CPUVector } from '../math'; |
||||||
|
import { GPUStruct } from './gpu-struct'; |
||||||
|
import { Matrix } from './matrix'; |
||||||
|
import { ScalarBatch } from './scalar-batch'; |
||||||
|
import { VectorBatch } from './vector-batch'; |
||||||
|
|
||||||
|
const SHADER_SCALE = fs.readFileSync('shader/bscale.wgsl').toString(); |
||||||
|
const SHADER_ISUM = fs.readFileSync('shader/isum.wgsl').toString(); |
||||||
|
const SHADER_MUL = fs.readFileSync('shader/mul.wgsl').toString(); |
||||||
|
const SHADER_TRANS = fs.readFileSync('shader/transpose.wgsl').toString(); |
||||||
|
const SHADER_AVG = fs.readFileSync('shader/avg.wgsl').toString(); |
||||||
|
const SHADER_TOT = fs.readFileSync('shader/tot.wgsl').toString(); |
||||||
|
|
||||||
|
export class MatrixBatch< |
||||||
|
L extends number, |
||||||
|
H extends number, |
||||||
|
W extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
> extends GPUStruct<A> { |
||||||
|
public get width(): W { |
||||||
|
return this.size[0] as W; |
||||||
|
} |
||||||
|
|
||||||
|
public get height(): H { |
||||||
|
return this.size[1] as H; |
||||||
|
} |
||||||
|
|
||||||
|
public get layers(): L { |
||||||
|
return this.size[2] as L; |
||||||
|
} |
||||||
|
|
||||||
|
protected override workgroups(pm: number = 1): [number, number, number] { |
||||||
|
return [this.width / GPUStruct.dunit / pm, this.height / GPUStruct.dunit, this.layers / MatrixBatch.lunit]; |
||||||
|
} |
||||||
|
|
||||||
|
protected override workgroup(): [number, number, number] { |
||||||
|
return [GPUStruct.dunit, GPUStruct.dunit, MatrixBatch.lunit]; |
||||||
|
} |
||||||
|
|
||||||
|
public constructor(device: GPUDevice, array: new (size: number) => A, data: CPUVector<L, CPUMatrix<H, W, T>>); |
||||||
|
public constructor(device: GPUDevice, array: new (size: number) => A, l: L, h: H, w: W, data?: CPUVector<L, CPUMatrix<H, W, T>>); |
||||||
|
public constructor(device: GPUDevice, array: new (size: number) => A, ...args: unknown[]) { |
||||||
|
let [l, h, w]: [number, number, number] = [1, 1, 1]; |
||||||
|
if (typeof args[0] === 'number') { |
||||||
|
[l, h, w] = args as [L, H, W]; |
||||||
|
} else { |
||||||
|
const [data] = args as [CPUVector<L, CPUMatrix<H, W, T>>]; |
||||||
|
[l, h, w] = [data.length, data[0].length, data[0][0].length, 1]; |
||||||
|
} |
||||||
|
|
||||||
|
super(device, array, `${h}x${w}[${l}]`, [w, h, l, 1]); |
||||||
|
|
||||||
|
MatrixBatch.assertPackable(1, this); |
||||||
|
|
||||||
|
const data = (typeof args[0] === 'number' ? args[3] : args[0]) as CPUVector<L, CPUMatrix<H, W, T>> | undefined; |
||||||
|
if (data) { |
||||||
|
this.set(data); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public index(l: number, y: number, x: number): number { |
||||||
|
return (l * this.height + y) * this.width + x; |
||||||
|
} |
||||||
|
|
||||||
|
public async set(y: number, x: number, v: T): Promise<void>; |
||||||
|
public async set(data: CPUVector<L, CPUMatrix<H, W, T>>): Promise<void>; |
||||||
|
public async set(slice: T[][][], ob?: number, oy?: number, ox?: number): Promise<void>; |
||||||
|
public async set(m: MatrixBatch<L, H, W, A, T>): Promise<void>; |
||||||
|
public async set(...args: unknown[]): Promise<void> { |
||||||
|
if (typeof args[0] === 'number') { |
||||||
|
const [l, y, x, v]: number[] = args as number[]; |
||||||
|
|
||||||
|
if (this._dirty === 'back') { |
||||||
|
await this.read(); |
||||||
|
} |
||||||
|
|
||||||
|
this.data[this.index(l, y, x)] = v; |
||||||
|
this._dirty = 'front'; |
||||||
|
} else if (Array.isArray(args[0])) { |
||||||
|
const [data, ol = 0, oy = 0, ox = 0] = args as [T[][][], number | undefined, number | undefined, number | undefined]; |
||||||
|
|
||||||
|
if (this._dirty === 'back' && (ol || oy || ox || data.length < this.layers || data[0].length < this.height || data[0][0].length < this.width)) { |
||||||
|
await this.read(); |
||||||
|
} |
||||||
|
|
||||||
|
let l = ol; |
||||||
|
let y = oy; |
||||||
|
let x = ox; |
||||||
|
for (const mat of data ?? []) { |
||||||
|
if (l >= this.layers) { |
||||||
|
break; |
||||||
|
} |
||||||
|
for (const row of mat ?? []) { |
||||||
|
if (y >= this.height) { |
||||||
|
break; |
||||||
|
} |
||||||
|
for (const v of row) { |
||||||
|
if (x >= this.width) { |
||||||
|
break; |
||||||
|
} |
||||||
|
this.data[this.index(l, y, x)] = v; |
||||||
|
x++; |
||||||
|
} |
||||||
|
x = ox; |
||||||
|
y++; |
||||||
|
} |
||||||
|
y = oy; |
||||||
|
l++; |
||||||
|
} |
||||||
|
this._dirty = 'front'; |
||||||
|
} else { |
||||||
|
const [batch] = args as [MatrixBatch<L, H, W, A, T>]; |
||||||
|
const copy = this._device.createCommandEncoder(); |
||||||
|
copy.copyBufferToBuffer(batch.buffer(), 0, this.buffer(), 0, this.byteLength); |
||||||
|
this._device.queue.submit([copy.finish()]); |
||||||
|
this._dirty = 'back'; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public async get(l: number, y: number, x: number): Promise<T>; |
||||||
|
public async get(l: number): Promise<CPUMatrix<H, W, T>>; |
||||||
|
public async get(): Promise<CPUVector<L, CPUMatrix<H, W, T>>>; |
||||||
|
public async get(l?: number, y?: number, x?: number): Promise<T | CPUMatrix<H, W, T> | CPUVector<L, CPUMatrix<H, W, T>>> { |
||||||
|
if (this._dirty === 'back') { |
||||||
|
await this.read(); |
||||||
|
} |
||||||
|
if (typeof x === 'number') { |
||||||
|
return this.data[this.index(l!, y!, x!)] as T; |
||||||
|
} else if (typeof l === 'number') { |
||||||
|
const result: CPUMatrix<H, W, T> = [] as unknown as CPUMatrix<H, W, T>; |
||||||
|
for (let y: number = 0; y < this.height; y++) { |
||||||
|
const row: CPUVector<W, T> = [] as unknown as CPUVector<W, T>; |
||||||
|
for (let x: number = 0; x < this.width; x++) { |
||||||
|
row.push(this.data[this.index(l, y, x)] as T); |
||||||
|
} |
||||||
|
result.push(row); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} else { |
||||||
|
const result: CPUVector<L, CPUMatrix<H, W, T>> = [] as unknown as CPUVector<L, CPUMatrix<H, W, T>>; |
||||||
|
for (let l: number = 0; l < this.layers; l++) { |
||||||
|
const layer: CPUMatrix<H, W, T> = [] as unknown as CPUMatrix<H, W, T>; |
||||||
|
for (let y: number = 0; y < this.height; y++) { |
||||||
|
const row: CPUVector<W, T> = [] as unknown as CPUVector<W, T>; |
||||||
|
for (let x: number = 0; x < this.width; x++) { |
||||||
|
row.push(this.data[this.index(l, y, x)] as T); |
||||||
|
} |
||||||
|
layer.push(row); |
||||||
|
} |
||||||
|
result.push(layer); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected static override assertPackable(pm: number, ...ms: MatrixBatch<number, number, number, Uint32Array | Int32Array | Float32Array>[]): void { |
||||||
|
for (const m of ms) { |
||||||
|
if (m.layers % MatrixBatch.lunit !== 0 || m.height % MatrixBatch.dunit !== 0 || m.width % (MatrixBatch.dunit * pm) !== 0) { |
||||||
|
throw new Error(`Batch size ${m.height}x${m.width}[${m.layers}] must be divisible by ${MatrixBatch.dunit}x${MatrixBatch.dunit * pm}[${MatrixBatch.lunit}].`); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static sum< |
||||||
|
L extends number, |
||||||
|
H extends number, |
||||||
|
W extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
>(l: MatrixBatch<L, H, W, A, T>, r: MatrixBatch<L, H, W, A, T>, result: MatrixBatch<L, H, W, A, T> = new MatrixBatch(l._device, l._array, l.layers, l.height, l.width)): MatrixBatch<L, H, W, A, T> { |
||||||
|
return GPUStruct.csum(l, r, result); |
||||||
|
} |
||||||
|
|
||||||
|
public static sub< |
||||||
|
L extends number, |
||||||
|
H extends number, |
||||||
|
W extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
>(l: MatrixBatch<L, H, W, A, T>, r: MatrixBatch<L, H, W, A, T>, result: MatrixBatch<L, H, W, A, T> = new MatrixBatch(l._device, l._array, l.layers, l.height, l.width)): MatrixBatch<L, H, W, A, T> { |
||||||
|
return GPUStruct.csub(l, r, result); |
||||||
|
} |
||||||
|
|
||||||
|
public static isum< |
||||||
|
L extends number, |
||||||
|
H extends number, |
||||||
|
I extends number, |
||||||
|
W extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>>, |
||||||
|
>( |
||||||
|
lis: VectorBatch<L, I, Int32Array>, |
||||||
|
m: MatrixBatch<L, H, W, A, T>, |
||||||
|
result: MatrixBatch<L, H, W, A, T> = new MatrixBatch(m._device, m._array, m.layers, m.height, m.width), |
||||||
|
): MatrixBatch<L, H, W, A, T> { |
||||||
|
this.assertUnique(m, result); |
||||||
|
this.assertColocated(m, result); |
||||||
|
this.assertPackable(4, m, result); |
||||||
|
|
||||||
|
return result.calculate('isum', SHADER_ISUM, [lis, m], false, 4); |
||||||
|
} |
||||||
|
|
||||||
|
public static scale< |
||||||
|
L extends number, |
||||||
|
H extends number, |
||||||
|
W extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
>(l: ScalarBatch<L, A, T>, r: MatrixBatch<L, H, W, A, T>, result: MatrixBatch<L, H, W, A, T> = new MatrixBatch(r._device, r._array, r.layers, r.height, r.width)): MatrixBatch<L, H, W, A, T> { |
||||||
|
this.assertUnique(l, r); |
||||||
|
this.assertUnique(l, result); |
||||||
|
this.assertColocated(l, r, result); |
||||||
|
this.assertPackable(4, r, result); |
||||||
|
|
||||||
|
return result.calculate('scale', SHADER_SCALE, result === r ? [l] : [l, r], result === r, 4); |
||||||
|
} |
||||||
|
|
||||||
|
public static mul< |
||||||
|
L extends number, |
||||||
|
H extends number, |
||||||
|
S extends number, |
||||||
|
W extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>>, |
||||||
|
>( |
||||||
|
l: MatrixBatch<L, H, S, A, T>, |
||||||
|
r: MatrixBatch<L, S, W, A, T>, |
||||||
|
result: MatrixBatch<L, H, W, A, T> = new MatrixBatch(l._device, l._array, l.layers, l.height, r.width), |
||||||
|
): MatrixBatch<L, H, W, A, T> { |
||||||
|
this.assertUnique(l, r, result); |
||||||
|
this.assertColocated(l, r, result); |
||||||
|
this.assertPackable(1, l, r, result); |
||||||
|
|
||||||
|
return result.calculate('mul', SHADER_MUL, [l, r]); |
||||||
|
} |
||||||
|
|
||||||
|
public static transpose< |
||||||
|
L extends number, |
||||||
|
H extends number, |
||||||
|
W extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>>, |
||||||
|
>( |
||||||
|
m: MatrixBatch<L, H, W, A, T>, |
||||||
|
result: MatrixBatch<L, W, H, A, T> = new MatrixBatch(m._device, m._array, m.layers, m.width, m.height), |
||||||
|
): MatrixBatch<L, W, H, A, T> { |
||||||
|
this.assertUnique(m, result); |
||||||
|
this.assertColocated(m, result); |
||||||
|
this.assertPackable(1, m, result); |
||||||
|
|
||||||
|
return result.calculate('transpose', SHADER_TRANS, [m]); |
||||||
|
} |
||||||
|
|
||||||
|
public static avg< |
||||||
|
L extends number, |
||||||
|
H extends number, |
||||||
|
W extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>>, |
||||||
|
>( |
||||||
|
m: MatrixBatch<L, H, W, A, T>, |
||||||
|
result: Matrix<H, W, A, T> = new Matrix(m._device, m._array, m.height, m.width), |
||||||
|
): Matrix<H, W, A, T> { |
||||||
|
this.assertUnique(m, result); |
||||||
|
this.assertColocated(m, result); |
||||||
|
this.assertPackable(4, m); |
||||||
|
|
||||||
|
return result.calculate('avg', SHADER_AVG, [m], false, 4); |
||||||
|
} |
||||||
|
|
||||||
|
public static tot< |
||||||
|
L extends number, |
||||||
|
H extends number, |
||||||
|
W extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>>, |
||||||
|
>( |
||||||
|
m: MatrixBatch<L, H, W, A, T>, |
||||||
|
result: Matrix<H, W, A, T> = new Matrix(m._device, m._array, m.height, m.width), |
||||||
|
): Matrix<H, W, A, T> { |
||||||
|
this.assertUnique(m, result); |
||||||
|
this.assertColocated(m, result); |
||||||
|
this.assertPackable(4, m); |
||||||
|
|
||||||
|
return result.calculate('tot', SHADER_TOT, [m], false, 4); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,233 @@ |
|||||||
|
import fs from 'node:fs'; |
||||||
|
import { Matrix as CPUMatrix, Vector as CPUVector } from '../math'; |
||||||
|
import { GPUStruct } from './gpu-struct'; |
||||||
|
import { MatrixBatch } from './matrix-batch'; |
||||||
|
import { Scalar } from './scalar'; |
||||||
|
import { Vector } from './vector'; |
||||||
|
|
||||||
|
const SHADER_ISUM = fs.readFileSync('shader/isum.wgsl').toString(); |
||||||
|
const SHADER_MUL = fs.readFileSync('shader/mul.wgsl').toString(); |
||||||
|
const SHADER_TRANS = fs.readFileSync('shader/transpose.wgsl').toString(); |
||||||
|
const SHADER_UNAVG = fs.readFileSync('shader/unavg.wgsl').toString(); |
||||||
|
|
||||||
|
export class Matrix< |
||||||
|
H extends number, |
||||||
|
W extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
> extends GPUStruct<A> { |
||||||
|
public get width(): W { |
||||||
|
return this.size[0] as W; |
||||||
|
} |
||||||
|
|
||||||
|
public get height(): H { |
||||||
|
return this.size[1] as H; |
||||||
|
} |
||||||
|
|
||||||
|
protected override workgroups(pm: number = 1): [number, number, number] { |
||||||
|
return [this.width / GPUStruct.dunit / pm, this.height / GPUStruct.dunit, 1]; |
||||||
|
} |
||||||
|
|
||||||
|
protected override workgroup(): [number, number, number] { |
||||||
|
return [GPUStruct.dunit, GPUStruct.dunit, 1]; |
||||||
|
} |
||||||
|
|
||||||
|
public constructor(device: GPUDevice, array: new (size: number) => A, data: CPUMatrix<H, W, T>); |
||||||
|
public constructor(device: GPUDevice, array: new (size: number) => A, h: H, w: W, data?: CPUMatrix<H, W, T>); |
||||||
|
public constructor(device: GPUDevice, array: new (size: number) => A, ...args: unknown[]) { |
||||||
|
let [h, w]: [number, number] = [1, 1]; |
||||||
|
if (typeof args[0] === 'number') { |
||||||
|
[h, w] = args as [H, W]; |
||||||
|
} else { |
||||||
|
const [data] = args as [CPUMatrix<H, W, T>]; |
||||||
|
[h, w] = [data.length, data[0].length]; |
||||||
|
} |
||||||
|
|
||||||
|
super(device, array, `${h}x${w}`, [w, h, 1, 1]); |
||||||
|
|
||||||
|
Matrix.assertPackable(1, this); |
||||||
|
|
||||||
|
const data = (typeof args[0] === 'number' ? args[2] : args[0]) as CPUMatrix<H, W, T> | undefined; |
||||||
|
if (data) { |
||||||
|
this.set(data); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public index(y: number, x: number): number { |
||||||
|
return y * this.width + x; |
||||||
|
} |
||||||
|
|
||||||
|
public async set(y: number, x: number, v: T): Promise<void>; |
||||||
|
public async set(data: CPUMatrix<H, W, T>): Promise<void>; |
||||||
|
public async set(slice: T[][], oy?: number, ox?: number): Promise<void>; |
||||||
|
public async set(m: Matrix<H, W, A, T>): Promise<void>; |
||||||
|
public async set(...args: unknown[]): Promise<void> { |
||||||
|
if (typeof args[0] === 'number') { |
||||||
|
const [y, x, v]: number[] = args as number[]; |
||||||
|
|
||||||
|
if (this._dirty === 'back') { |
||||||
|
await this.read(); |
||||||
|
} |
||||||
|
|
||||||
|
this.data[this.index(y, x)] = v; |
||||||
|
this._dirty = 'front'; |
||||||
|
} else if (Array.isArray(args[0])) { |
||||||
|
const [data, oy = 0, ox = 0] = args as [T[][], number | undefined, number | undefined]; |
||||||
|
|
||||||
|
if (this._dirty === 'back' && (oy || ox || data.length < this.height || data[0].length < this.width)) { |
||||||
|
await this.read(); |
||||||
|
} |
||||||
|
|
||||||
|
let y = oy; |
||||||
|
let x = ox; |
||||||
|
for (const row of data ?? []) { |
||||||
|
if (y >= this.height) { |
||||||
|
break; |
||||||
|
} |
||||||
|
for (const v of row) { |
||||||
|
if (x >= this.width) { |
||||||
|
break; |
||||||
|
} |
||||||
|
this.data[this.index(y, x)] = v; |
||||||
|
x++; |
||||||
|
} |
||||||
|
x = ox; |
||||||
|
y++; |
||||||
|
} |
||||||
|
this._dirty = 'front'; |
||||||
|
} else { |
||||||
|
const [matrix] = args as [Matrix<H, W, A, T>]; |
||||||
|
const copy = this._device.createCommandEncoder(); |
||||||
|
copy.copyBufferToBuffer(matrix.buffer(), 0, this.buffer(), 0, this.byteLength); |
||||||
|
this._device.queue.submit([copy.finish()]); |
||||||
|
this._dirty = 'back'; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public async get(y: number, x: number): Promise<T>; |
||||||
|
public async get(): Promise<CPUMatrix<H, W, T>>; |
||||||
|
public async get(y?: number, x?: number): Promise<T | CPUMatrix<H, W, T>> { |
||||||
|
if (this._dirty === 'back') { |
||||||
|
await this.read(); |
||||||
|
} |
||||||
|
if (typeof x === 'number') { |
||||||
|
return this.data[this.index(y!, x!)] as T; |
||||||
|
} else { |
||||||
|
const result: CPUMatrix<H, W, T> = [] as unknown as CPUMatrix<H, W, T>; |
||||||
|
for (let y: number = 0; y < this.height; y++) { |
||||||
|
const row: CPUVector<W, T> = [] as unknown as CPUVector<W, T>; |
||||||
|
for (let x: number = 0; x < this.width; x++) { |
||||||
|
row.push(this.data[this.index(y, x)] as T); |
||||||
|
} |
||||||
|
result.push(row); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected static override assertPackable(pm: number, ...ms: Matrix<number, number, Uint32Array | Int32Array | Float32Array>[]): void { |
||||||
|
for (const m of ms) { |
||||||
|
if (m.height % Matrix.dunit !== 0 || m.width % (Matrix.dunit * pm) !== 0) { |
||||||
|
throw new Error(`Matrix size ${m.height}x${m.width} must be divisible by ${Matrix.dunit}x${Matrix.dunit * pm}.`); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static sum< |
||||||
|
H extends number, |
||||||
|
W extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
>(l: Matrix<H, W, A, T>, r: Matrix<H, W, A, T>, result: Matrix<H, W, A, T> = new Matrix(l._device, l._array, l.height, l.width)): Matrix<H, W, A, T> { |
||||||
|
return GPUStruct.csum(l, r, result); |
||||||
|
} |
||||||
|
|
||||||
|
public static sub< |
||||||
|
H extends number, |
||||||
|
W extends number, |
||||||
|
|
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
>(l: Matrix<H, W, A, T>, r: Matrix<H, W, A, T>, result: Matrix<H, W, A, T> = new Matrix(l._device, l._array, l.height, l.width)): Matrix<H, W, A, T> { |
||||||
|
return GPUStruct.csub(l, r, result); |
||||||
|
} |
||||||
|
|
||||||
|
public static isum< |
||||||
|
H extends number, |
||||||
|
I extends number, |
||||||
|
W extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>>, |
||||||
|
>( |
||||||
|
lis: Vector<I, Int32Array>, |
||||||
|
m: Matrix<H, W, A, T>, |
||||||
|
result: Matrix<H, W, A, T> = new Matrix(m._device, m._array, m.height, m.width), |
||||||
|
): Matrix<H, W, A, T> { |
||||||
|
this.assertUnique(m, result); |
||||||
|
this.assertColocated(m, result); |
||||||
|
this.assertPackable(4, m, result); |
||||||
|
|
||||||
|
return result.calculate('isum', SHADER_ISUM, [lis, m], false, 4); |
||||||
|
} |
||||||
|
|
||||||
|
public static scale< |
||||||
|
H extends number, |
||||||
|
W extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
>(l: Scalar<A, T>, r: Matrix<H, W, A, T>, result: Matrix<H, W, A, T> = new Matrix(r._device, r._array, r.height, r.width)): Matrix<H, W, A, T> { |
||||||
|
return GPUStruct.cscale(l, r, result); |
||||||
|
} |
||||||
|
|
||||||
|
public static mul< |
||||||
|
H extends number, |
||||||
|
S extends number, |
||||||
|
W extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>>, |
||||||
|
>( |
||||||
|
l: Matrix<H, S, A, T>, |
||||||
|
r: Matrix<S, W, A, T>, |
||||||
|
result: Matrix<H, W, A, T> = new Matrix(l._device, l._array, l.height, r.width), |
||||||
|
): Matrix<H, W, A, T> { |
||||||
|
this.assertUnique(l, r, result); |
||||||
|
this.assertColocated(l, r, result); |
||||||
|
this.assertPackable(1, l, r, result); |
||||||
|
|
||||||
|
return result.calculate('mul', SHADER_MUL, [l, r]); |
||||||
|
} |
||||||
|
|
||||||
|
public static transpose< |
||||||
|
H extends number, |
||||||
|
W extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>>, |
||||||
|
>( |
||||||
|
m: Matrix<H, W, A, T>, |
||||||
|
result: Matrix<W, H, A, T> = new Matrix(m._device, m._array, m.width, m.height), |
||||||
|
): Matrix<W, H, A, T> { |
||||||
|
this.assertUnique(m, result); |
||||||
|
this.assertColocated(m, result); |
||||||
|
this.assertPackable(1, m, result); |
||||||
|
|
||||||
|
return result.calculate('transpose', SHADER_TRANS, [m]); |
||||||
|
} |
||||||
|
|
||||||
|
public static unavg< |
||||||
|
L extends number, |
||||||
|
H extends number, |
||||||
|
W extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>>, |
||||||
|
>( |
||||||
|
l: L, |
||||||
|
m: Matrix<H, W, A, T>, |
||||||
|
result: MatrixBatch<L, H, W, A, T> = new MatrixBatch(m._device, m._array, l, m.height, m.width), |
||||||
|
): MatrixBatch<L, H, W, A, T> { |
||||||
|
this.assertUnique(m, result); |
||||||
|
this.assertColocated(m, result); |
||||||
|
this.assertPackable(4, m); |
||||||
|
|
||||||
|
return result.calculate('unavg', SHADER_UNAVG, [m], false, 4); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,176 @@ |
|||||||
|
import fs from 'node:fs'; |
||||||
|
import { Vector as CPUVector } from '../math'; |
||||||
|
import { GPUStruct } from './gpu-struct'; |
||||||
|
import { Scalar } from './scalar'; |
||||||
|
|
||||||
|
const SHADER_AVG = fs.readFileSync('shader/avg.wgsl').toString(); |
||||||
|
const SHADER_TOT = fs.readFileSync('shader/tot.wgsl').toString(); |
||||||
|
|
||||||
|
export class ScalarBatch< |
||||||
|
L extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
> extends GPUStruct<A> { |
||||||
|
public get layers(): L { |
||||||
|
return this.size[2] as L; |
||||||
|
} |
||||||
|
|
||||||
|
protected override workgroups(): [number, number, number] { |
||||||
|
return [1, 1, this.layers / GPUStruct.lunit]; |
||||||
|
} |
||||||
|
|
||||||
|
protected override workgroup(): [number, number, number] { |
||||||
|
return [1, 1, GPUStruct.lunit]; |
||||||
|
} |
||||||
|
|
||||||
|
public constructor(device: GPUDevice, array: new (size: number) => A, data: CPUVector<L, T>); |
||||||
|
public constructor(device: GPUDevice, array: new (size: number) => A, l: L, data?: CPUVector<L, T>); |
||||||
|
public constructor(device: GPUDevice, array: new (size: number) => A, ...args: unknown[]) { |
||||||
|
const l: number = typeof args[0] === 'number' |
||||||
|
? args[0] as L |
||||||
|
: (args[0] as CPUVector<L, T>).length; |
||||||
|
|
||||||
|
super(device, array, `[${l}]`, [1, 1, l, 1]); |
||||||
|
|
||||||
|
ScalarBatch.assertPackable(1, this); |
||||||
|
|
||||||
|
const data = (typeof args[0] === 'number' ? args[1] : args[0]) as CPUVector<L, T> | undefined; |
||||||
|
if (data) { |
||||||
|
this.set(data); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public async set(l: number, v: T): Promise<void>; |
||||||
|
public async set(data: CPUVector<L, T>): Promise<void>; |
||||||
|
public async set(slice: T[], ol?: number): Promise<void>; |
||||||
|
public async set(b: ScalarBatch<L, A, T>): Promise<void>; |
||||||
|
public async set(...args: unknown[]): Promise<void> { |
||||||
|
if (typeof args[0] === 'number') { |
||||||
|
const [l, v]: number[] = args as number[]; |
||||||
|
|
||||||
|
if (this._dirty === 'back') { |
||||||
|
await this.read(); |
||||||
|
} |
||||||
|
|
||||||
|
this.data[l] = v; |
||||||
|
this._dirty = 'front'; |
||||||
|
} else if (Array.isArray(args[0])) { |
||||||
|
const [data, ol = 0] = args as [T[], number | undefined]; |
||||||
|
|
||||||
|
if (this._dirty === 'back' && (ol || data.length < this.layers)) { |
||||||
|
await this.read(); |
||||||
|
} |
||||||
|
|
||||||
|
let l = ol; |
||||||
|
for (const v of data ?? []) { |
||||||
|
if (l >= this.layers) { |
||||||
|
break; |
||||||
|
} |
||||||
|
this.data[l] = v; |
||||||
|
l++; |
||||||
|
} |
||||||
|
this._dirty = 'front'; |
||||||
|
} else { |
||||||
|
const [batch] = args as [ScalarBatch<L, A, T>]; |
||||||
|
const copy = this._device.createCommandEncoder(); |
||||||
|
copy.copyBufferToBuffer(batch.buffer(), 0, this.buffer(), 0, this.byteLength); |
||||||
|
this._device.queue.submit([copy.finish()]); |
||||||
|
this._dirty = 'back'; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public async get(l: number): Promise<T>; |
||||||
|
public async get(): Promise<CPUVector<L, T>>; |
||||||
|
public async get(l?: number): Promise<T | CPUVector<L, T>> { |
||||||
|
if (this._dirty === 'back') { |
||||||
|
await this.read(); |
||||||
|
} |
||||||
|
if (typeof l === 'number') { |
||||||
|
return this.data[l] as T; |
||||||
|
} else { |
||||||
|
const result: CPUVector<L, T> = [] as unknown as CPUVector<L, T>; |
||||||
|
for (let l: number = 0; l < this.layers; l++) { |
||||||
|
result.push(this.data[l] as T); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected static override assertPackable(_: number, ...ms: ScalarBatch<number, Uint32Array | Int32Array | Float32Array>[]): void { |
||||||
|
for (const m of ms) { |
||||||
|
if (m.layers % ScalarBatch.dunit !== 0) { |
||||||
|
throw new Error(`Batch size [${m.layers}] must be divisible by [${ScalarBatch.dunit}].`); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static sum< |
||||||
|
L extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
>(l: ScalarBatch<L, A, T>, r: ScalarBatch<L, A, T>, result: ScalarBatch<L, A, T> = new ScalarBatch(l._device, l._array, l.layers)): ScalarBatch<L, A, T> { |
||||||
|
return GPUStruct.csum(l, r, result); |
||||||
|
} |
||||||
|
|
||||||
|
public static sub< |
||||||
|
L extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
>(l: ScalarBatch<L, A, T>, r: ScalarBatch<L, A, T>, result: ScalarBatch<L, A, T> = new ScalarBatch(l._device, l._array, l.layers)): ScalarBatch<L, A, T> { |
||||||
|
return GPUStruct.csub(l, r, result); |
||||||
|
} |
||||||
|
|
||||||
|
public static mul< |
||||||
|
L extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
>(l: ScalarBatch<L, A, T>, r: ScalarBatch<L, A, T>, result: ScalarBatch<L, A, T> = new ScalarBatch(l._device, l._array, l.layers)): ScalarBatch<L, A, T> { |
||||||
|
return GPUStruct.cmul(l, r, result); |
||||||
|
} |
||||||
|
|
||||||
|
public static div< |
||||||
|
L extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
>(l: ScalarBatch<L, A, T>, r: ScalarBatch<L, A, T>, result: ScalarBatch<L, A, T> = new ScalarBatch(l._device, l._array, l.layers)): ScalarBatch<L, A, T> { |
||||||
|
return GPUStruct.cdiv(l, r, result); |
||||||
|
} |
||||||
|
|
||||||
|
public static scale< |
||||||
|
L extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
>(l: Scalar<A, T>, r: ScalarBatch<L, A, T>, result: ScalarBatch<L, A, T> = new ScalarBatch(r._device, r._array, r.layers)): ScalarBatch<L, A, T> { |
||||||
|
return GPUStruct.cscale(l, r, result); |
||||||
|
} |
||||||
|
|
||||||
|
public static avg< |
||||||
|
L extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>>, |
||||||
|
>( |
||||||
|
m: ScalarBatch<L, A, T>, |
||||||
|
result: Scalar<A, T> = new Scalar(m._device, m._array), |
||||||
|
): Scalar<A, T> { |
||||||
|
this.assertUnique(m, result); |
||||||
|
this.assertColocated(m, result); |
||||||
|
this.assertPackable(4, m); |
||||||
|
|
||||||
|
return result.calculate('avg', SHADER_AVG, [m], false, 4); |
||||||
|
} |
||||||
|
|
||||||
|
public static tot< |
||||||
|
L extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>>, |
||||||
|
>( |
||||||
|
m: ScalarBatch<L, A, T>, |
||||||
|
result: Scalar<A, T> = new Scalar(m._device, m._array), |
||||||
|
): Scalar<A, T> { |
||||||
|
this.assertUnique(m, result); |
||||||
|
this.assertColocated(m, result); |
||||||
|
this.assertPackable(4, m); |
||||||
|
|
||||||
|
return result.calculate('tot', SHADER_TOT, [m], false, 4); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,90 @@ |
|||||||
|
import fs from 'node:fs'; |
||||||
|
import { GPUStruct } from './gpu-struct'; |
||||||
|
import { ScalarBatch } from './scalar-batch'; |
||||||
|
|
||||||
|
const SHADER_UNAVG = fs.readFileSync('shader/unavg.wgsl').toString(); |
||||||
|
|
||||||
|
export class Scalar< |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
> extends GPUStruct<A> { |
||||||
|
protected override workgroups(): [number, number, number] { |
||||||
|
return [1, 1, 1]; |
||||||
|
} |
||||||
|
|
||||||
|
protected override workgroup(): [number, number, number] { |
||||||
|
return [1, 1, 1]; |
||||||
|
} |
||||||
|
|
||||||
|
public constructor(device: GPUDevice, array: new (size: number) => A, data?: T) { |
||||||
|
super(device, array, 'scalar', [1, 1, 1, 1]); |
||||||
|
this.set(data || 0 as T); |
||||||
|
} |
||||||
|
|
||||||
|
public async set(v: T | Scalar<A, T>): Promise<void> { |
||||||
|
if (typeof v === 'number') { |
||||||
|
if (this._dirty === 'back') { |
||||||
|
await this.read(); |
||||||
|
} |
||||||
|
|
||||||
|
this.data[0] = v; |
||||||
|
this._dirty = 'front'; |
||||||
|
} else { |
||||||
|
const copy = this._device.createCommandEncoder(); |
||||||
|
copy.copyBufferToBuffer(v.buffer(), 0, this.buffer(), 0, this.byteLength); |
||||||
|
this._device.queue.submit([copy.finish()]); |
||||||
|
this._dirty = 'back'; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public async get(): Promise<T> { |
||||||
|
if (this._dirty === 'back') { |
||||||
|
await this.read(); |
||||||
|
} |
||||||
|
return this.data[0] as T; |
||||||
|
} |
||||||
|
|
||||||
|
public static sum< |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
>(l: Scalar<A, T>, r: Scalar<A, T>, result: Scalar<A, T> = new Scalar(l._device, l._array)): Scalar<A, T> { |
||||||
|
return GPUStruct.csum(l, r, result); |
||||||
|
} |
||||||
|
|
||||||
|
public static sub< |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
>(l: Scalar<A, T>, r: Scalar<A, T>, result: Scalar<A, T> = new Scalar(l._device, l._array)): Scalar<A, T> { |
||||||
|
return GPUStruct.csub(l, r, result); |
||||||
|
} |
||||||
|
|
||||||
|
public static mul< |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
>(l: Scalar<A, T>, r: Scalar<A, T>, result: Scalar<A, T> = new Scalar(l._device, l._array)): Scalar<A, T> { |
||||||
|
return GPUStruct.cmul(l, r, result); |
||||||
|
} |
||||||
|
|
||||||
|
public static div< |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
>(l: Scalar<A, T>, r: Scalar<A, T>, result: Scalar<A, T> = new Scalar(l._device, l._array)): Scalar<A, T> { |
||||||
|
return GPUStruct.cdiv(l, r, result); |
||||||
|
} |
||||||
|
|
||||||
|
public static unavg< |
||||||
|
L extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>>, |
||||||
|
>( |
||||||
|
l: L, |
||||||
|
m: Scalar<A, T>, |
||||||
|
result: ScalarBatch<L, A, T> = new ScalarBatch(m._device, m._array, l), |
||||||
|
): ScalarBatch<L, A, T> { |
||||||
|
this.assertUnique(m, result); |
||||||
|
this.assertColocated(m, result); |
||||||
|
this.assertPackable(4, m); |
||||||
|
|
||||||
|
return result.calculate('unavg', SHADER_UNAVG, [m], false, 4); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,221 @@ |
|||||||
|
import fs from 'node:fs'; |
||||||
|
import { Vector as CPUVector } from '../math'; |
||||||
|
import { GPUStruct } from './gpu-struct'; |
||||||
|
import { ScalarBatch } from './scalar-batch'; |
||||||
|
import { Vector } from './vector'; |
||||||
|
|
||||||
|
const SHADER_AVG = fs.readFileSync('shader/avg.wgsl').toString(); |
||||||
|
const SHADER_TOT = fs.readFileSync('shader/tot.wgsl').toString(); |
||||||
|
const SHADER_SCALE = fs.readFileSync('shader/bscale.wgsl').toString(); |
||||||
|
const SHADER_DOT = fs.readFileSync('shader/mul.wgsl').toString(); |
||||||
|
|
||||||
|
export class VectorBatch< |
||||||
|
L extends number, |
||||||
|
D extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
> extends GPUStruct<A> { |
||||||
|
public get dims(): D { |
||||||
|
return this.size[0] as D; |
||||||
|
} |
||||||
|
|
||||||
|
public get layers(): L { |
||||||
|
return this.size[2] as L; |
||||||
|
} |
||||||
|
|
||||||
|
protected override workgroups(pm: number = 1): [number, number, number] { |
||||||
|
return [this.dims / GPUStruct.dunit / pm, 1, this.layers / GPUStruct.lunit]; |
||||||
|
} |
||||||
|
|
||||||
|
protected override workgroup(): [number, number, number] { |
||||||
|
return [GPUStruct.dunit, 1, GPUStruct.lunit]; |
||||||
|
} |
||||||
|
|
||||||
|
public constructor(device: GPUDevice, array: new (size: number) => A, data: CPUVector<L, CPUVector<D, T>>); |
||||||
|
public constructor(device: GPUDevice, array: new (size: number) => A, l: L, d: D, data?: CPUVector<L, CPUVector<D, T>>); |
||||||
|
public constructor(device: GPUDevice, array: new (size: number) => A, ...args: unknown[]) { |
||||||
|
let [l, d]: [number, number] = [1, 1]; |
||||||
|
if (typeof args[0] === 'number') { |
||||||
|
[l, d] = args as [L, D]; |
||||||
|
} else { |
||||||
|
const [data] = args as [CPUVector<L, CPUVector<D, T>>]; |
||||||
|
[l, d] = [data.length, data[0].length]; |
||||||
|
} |
||||||
|
|
||||||
|
super(device, array, `${d}[${l}]`, [d, 1, l, 1]); |
||||||
|
|
||||||
|
VectorBatch.assertPackable(1, this); |
||||||
|
|
||||||
|
const data = (typeof args[0] === 'number' ? args[2] : args[0]) as CPUVector<L, CPUVector<D, T>> | undefined; |
||||||
|
if (data) { |
||||||
|
this.set(data); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public index(l: number, i: number): number { |
||||||
|
return l * this.dims + i; |
||||||
|
} |
||||||
|
|
||||||
|
public async set(l: number, i: number, v: T): Promise<void>; |
||||||
|
public async set(data: CPUVector<L, CPUVector<D, T>>): Promise<void>; |
||||||
|
public async set(slice: T[][], ol?: number, oi?: number): Promise<void>; |
||||||
|
public async set(b: VectorBatch<L, D, A, T>): Promise<void>; |
||||||
|
public async set(...args: unknown[]): Promise<void> { |
||||||
|
if (typeof args[0] === 'number') { |
||||||
|
const [l, i, v]: number[] = args as number[]; |
||||||
|
|
||||||
|
if (this._dirty === 'back') { |
||||||
|
await this.read(); |
||||||
|
} |
||||||
|
|
||||||
|
this.data[this.index(l, i)] = v; |
||||||
|
this._dirty = 'front'; |
||||||
|
} else if (Array.isArray(args[0])) { |
||||||
|
const [data, ol = 0, oi = 0] = args as [T[][], number | undefined, number | undefined]; |
||||||
|
|
||||||
|
if (this._dirty === 'back' && (ol || oi || data.length < this.layers || data[0].length < this.dims)) { |
||||||
|
await this.read(); |
||||||
|
} |
||||||
|
|
||||||
|
let l = ol; |
||||||
|
let i = oi; |
||||||
|
for (const vec of data ?? []) { |
||||||
|
if (l >= this.layers) { |
||||||
|
break; |
||||||
|
} |
||||||
|
for (const v of vec) { |
||||||
|
if (i >= this.dims) { |
||||||
|
break; |
||||||
|
} |
||||||
|
this.data[this.index(l, i)] = v; |
||||||
|
i++; |
||||||
|
} |
||||||
|
i = oi; |
||||||
|
l++; |
||||||
|
} |
||||||
|
this._dirty = 'front'; |
||||||
|
} else { |
||||||
|
const [batch] = args as [VectorBatch<L, D, A, T>]; |
||||||
|
const copy = this._device.createCommandEncoder(); |
||||||
|
copy.copyBufferToBuffer(batch.buffer(), 0, this.buffer(), 0, this.byteLength); |
||||||
|
this._device.queue.submit([copy.finish()]); |
||||||
|
this._dirty = 'back'; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public async get(l: number, i: number): Promise<T>; |
||||||
|
public async get(l: number): Promise<CPUVector<D, T>>; |
||||||
|
public async get(): Promise<CPUVector<L, CPUVector<D, T>>>; |
||||||
|
public async get(l?: number, i?: number): Promise<T | CPUVector<D, T> | CPUVector<L, CPUVector<D, T>>> { |
||||||
|
if (this._dirty === 'back') { |
||||||
|
await this.read(); |
||||||
|
} |
||||||
|
if (typeof i === 'number') { |
||||||
|
return this.data[this.index(l!, i!)] as T; |
||||||
|
} else if (typeof l === 'number') { |
||||||
|
const result: CPUVector<D, T> = [] as unknown as CPUVector<D, T>; |
||||||
|
for (let i: number = 0; i < this.dims; i++) { |
||||||
|
result.push(this.data[this.index(l, i)] as T); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} else { |
||||||
|
const result: CPUVector<L, CPUVector<D, T>> = [] as unknown as CPUVector<L, CPUVector<D, T>>; |
||||||
|
for (let l: number = 0; l < this.layers; l++) { |
||||||
|
const vec: CPUVector<D, T> = [] as unknown as CPUVector<D, T>; |
||||||
|
for (let i: number = 0; i < this.dims; i++) { |
||||||
|
vec.push(this.data[this.index(l, i)] as T); |
||||||
|
} |
||||||
|
result.push(vec); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected static override assertPackable(pm: number, ...ms: VectorBatch<number, number, Uint32Array | Int32Array | Float32Array>[]): void { |
||||||
|
for (const m of ms) { |
||||||
|
if (m.layers % VectorBatch.lunit !== 0 || m.dims % (VectorBatch.dunit * pm) !== 0) { |
||||||
|
throw new Error(`Batch size ${m.dims}[${m.layers}] must be divisible by ${VectorBatch.dunit * pm}[${VectorBatch.lunit}].`); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static sum< |
||||||
|
L extends number, |
||||||
|
D extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
>(l: VectorBatch<L, D, A, T>, r: VectorBatch<L, D, A, T>, result: VectorBatch<L, D, A, T> = new VectorBatch(l._device, l._array, l.layers, l.dims)): VectorBatch<L, D, A, T> { |
||||||
|
return GPUStruct.csum(l, r, result); |
||||||
|
} |
||||||
|
|
||||||
|
public static sub< |
||||||
|
L extends number, |
||||||
|
D extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
>(l: VectorBatch<L, D, A, T>, r: VectorBatch<L, D, A, T>, result: VectorBatch<L, D, A, T> = new VectorBatch(l._device, l._array, l.layers, l.dims)): VectorBatch<L, D, A, T> { |
||||||
|
return GPUStruct.csub(l, r, result); |
||||||
|
} |
||||||
|
|
||||||
|
public static mul< |
||||||
|
L extends number, |
||||||
|
D extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
>(l: ScalarBatch<L, A, T>, r: VectorBatch<L, D, A, T>, result: VectorBatch<L, D, A, T> = new VectorBatch(r._device, r._array, r.layers, r.dims)): VectorBatch<L, D, A, T> { |
||||||
|
this.assertUnique(l, r); |
||||||
|
this.assertUnique(l, result); |
||||||
|
this.assertColocated(l, r, result); |
||||||
|
this.assertPackable(4, r, result); |
||||||
|
|
||||||
|
return result.calculate('scale', SHADER_SCALE, result === r ? [l] : [l, r], result === r, 4); |
||||||
|
} |
||||||
|
|
||||||
|
public static dot< |
||||||
|
L extends number, |
||||||
|
D extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
>(l: VectorBatch<L, D, A, T>, r: VectorBatch<L, D, A, T>, result: ScalarBatch<L, A, T> = new ScalarBatch(l._device, l._array, l.layers)): ScalarBatch<L, A, T> { |
||||||
|
throw new Error('Not implemented.'); |
||||||
|
|
||||||
|
this.assertUnique(l, r); |
||||||
|
this.assertUnique(l, result); |
||||||
|
this.assertColocated(l, r, result); |
||||||
|
// this.assertPackable(4, r, result);
|
||||||
|
|
||||||
|
return result.calculate('dot', SHADER_DOT, [l, r]); |
||||||
|
} |
||||||
|
|
||||||
|
public static avg< |
||||||
|
L extends number, |
||||||
|
D extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>>, |
||||||
|
>( |
||||||
|
m: VectorBatch<L, D, A, T>, |
||||||
|
result: Vector<D, A, T> = new Vector(m._device, m._array, m.dims), |
||||||
|
): Vector<D, A, T> { |
||||||
|
this.assertUnique(m, result); |
||||||
|
this.assertColocated(m, result); |
||||||
|
this.assertPackable(4, m); |
||||||
|
|
||||||
|
return result.calculate('avg', SHADER_AVG, [m], false, 4); |
||||||
|
} |
||||||
|
|
||||||
|
public static tot< |
||||||
|
L extends number, |
||||||
|
D extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>>, |
||||||
|
>( |
||||||
|
m: VectorBatch<L, D, A, T>, |
||||||
|
result: Vector<D, A, T> = new Vector(m._device, m._array, m.dims), |
||||||
|
): Vector<D, A, T> { |
||||||
|
this.assertUnique(m, result); |
||||||
|
this.assertColocated(m, result); |
||||||
|
this.assertPackable(4, m); |
||||||
|
|
||||||
|
return result.calculate('tot', SHADER_TOT, [m], false, 4); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,164 @@ |
|||||||
|
import fs from 'node:fs'; |
||||||
|
import { Vector as CPUVector } from '../math'; |
||||||
|
import { GPUStruct } from './gpu-struct'; |
||||||
|
import { Scalar } from './scalar'; |
||||||
|
import { VectorBatch } from './vector-batch'; |
||||||
|
|
||||||
|
const SHADER_UNAVG = fs.readFileSync('shader/unavg.wgsl').toString(); |
||||||
|
|
||||||
|
const SHADER_DOT = fs.readFileSync('shader/mul.wgsl').toString(); |
||||||
|
|
||||||
|
export class Vector< |
||||||
|
D extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
> extends GPUStruct<A> { |
||||||
|
public get dims(): D { |
||||||
|
return this.size[0] as D; |
||||||
|
} |
||||||
|
|
||||||
|
protected override workgroups(pm: number = 1): [number, number, number] { |
||||||
|
return [this.dims / GPUStruct.dunit / pm, 1, 1]; |
||||||
|
} |
||||||
|
|
||||||
|
protected override workgroup(): [number, number, number] { |
||||||
|
return [GPUStruct.dunit, 1, 1]; |
||||||
|
} |
||||||
|
|
||||||
|
public constructor(device: GPUDevice, array: new (size: number) => A, data: CPUVector<D, T>); |
||||||
|
public constructor(device: GPUDevice, array: new (size: number) => A, d: D, data?: CPUVector<D, T>); |
||||||
|
public constructor(device: GPUDevice, array: new (size: number) => A, ...args: unknown[]) { |
||||||
|
const d: number = typeof args[0] === 'number' |
||||||
|
? args[0] as D |
||||||
|
: (args[0] as CPUVector<D, T>).length; |
||||||
|
|
||||||
|
super(device, array, `${d}`, [d, 1, 1, 1]); |
||||||
|
|
||||||
|
Vector.assertPackable(1, this); |
||||||
|
|
||||||
|
const data = (typeof args[0] === 'number' ? args[1] : args[0]) as CPUVector<D, T> | undefined; |
||||||
|
if (data) { |
||||||
|
this.set(data); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public async set(i: number, v: T): Promise<void>; |
||||||
|
public async set(data: CPUVector<D, T>): Promise<void>; |
||||||
|
public async set(slice: T[], oi?: number): Promise<void>; |
||||||
|
public async set(b: Vector<D, A, T>): Promise<void>; |
||||||
|
public async set(...args: unknown[]): Promise<void> { |
||||||
|
if (typeof args[0] === 'number') { |
||||||
|
const [i, v]: number[] = args as number[]; |
||||||
|
|
||||||
|
if (this._dirty === 'back') { |
||||||
|
await this.read(); |
||||||
|
} |
||||||
|
|
||||||
|
this.data[i] = v; |
||||||
|
this._dirty = 'front'; |
||||||
|
} else if (Array.isArray(args[0])) { |
||||||
|
const [data, oi = 0] = args as [T[], number | undefined]; |
||||||
|
|
||||||
|
if (this._dirty === 'back' && (oi || data.length < this.dims)) { |
||||||
|
await this.read(); |
||||||
|
} |
||||||
|
|
||||||
|
let i = oi; |
||||||
|
for (const v of data ?? []) { |
||||||
|
if (i >= this.dims) { |
||||||
|
break; |
||||||
|
} |
||||||
|
this.data[i] = v; |
||||||
|
i++; |
||||||
|
} |
||||||
|
this._dirty = 'front'; |
||||||
|
} else { |
||||||
|
const [vector] = args as [Vector<D, A, T>]; |
||||||
|
const copy = this._device.createCommandEncoder(); |
||||||
|
copy.copyBufferToBuffer(vector.buffer(), 0, this.buffer(), 0, this.byteLength); |
||||||
|
this._device.queue.submit([copy.finish()]); |
||||||
|
this._dirty = 'back'; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public async get(i: number): Promise<T>; |
||||||
|
public async get(): Promise<CPUVector<D, T>>; |
||||||
|
public async get(i?: number): Promise<T | CPUVector<D, T>> { |
||||||
|
if (this._dirty === 'back') { |
||||||
|
await this.read(); |
||||||
|
} |
||||||
|
if (typeof i === 'number') { |
||||||
|
return this.data[i] as T; |
||||||
|
} else { |
||||||
|
const result: CPUVector<D, T> = [] as unknown as CPUVector<D, T>; |
||||||
|
for (let i: number = 0; i < this.dims; i++) { |
||||||
|
result.push(this.data[i] as T); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected static override assertPackable(pm: number, ...ms: Vector<number, Uint32Array | Int32Array | Float32Array>[]): void { |
||||||
|
for (const m of ms) { |
||||||
|
if (m.dims % (Vector.dunit * pm) !== 0) { |
||||||
|
throw new Error(`Vector size ${m.dims} must be divisible by ${Vector.dunit * pm}.`); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static sum< |
||||||
|
D extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
>(l: Vector<D, A, T>, r: Vector<D, A, T>, result: Vector<D, A, T> = new Vector(l._device, l._array, l.dims)): Vector<D, A, T> { |
||||||
|
return GPUStruct.csum(l, r, result); |
||||||
|
} |
||||||
|
|
||||||
|
public static sub< |
||||||
|
D extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
>(l: Vector<D, A, T>, r: Vector<D, A, T>, result: Vector<D, A, T> = new Vector(l._device, l._array, l.dims)): Vector<D, A, T> { |
||||||
|
return GPUStruct.csub(l, r, result); |
||||||
|
} |
||||||
|
|
||||||
|
public static mul< |
||||||
|
D extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
>(l: Scalar<A, T>, r: Vector<D, A, T>, result: Vector<D, A, T> = new Vector(r._device, r._array, r.dims)): Vector<D, A, T> { |
||||||
|
return GPUStruct.cscale(l, r, result); |
||||||
|
} |
||||||
|
|
||||||
|
public static dot< |
||||||
|
D extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>> = NonNullable<ReturnType<A['at']>>, |
||||||
|
>(l: Vector<D, A, T>, r: Vector<D, A, T>, result: Scalar<A, T> = new Scalar(l._device, l._array)): Scalar<A, T> { |
||||||
|
throw new Error('Not implemented.'); |
||||||
|
|
||||||
|
this.assertUnique(l, r); |
||||||
|
this.assertUnique(l, result); |
||||||
|
this.assertColocated(l, r, result); |
||||||
|
// this.assertPackable(4, r, result);
|
||||||
|
|
||||||
|
return result.calculate('dot', SHADER_DOT, [l, r]); |
||||||
|
} |
||||||
|
|
||||||
|
public static unavg< |
||||||
|
L extends number, |
||||||
|
D extends number, |
||||||
|
A extends Uint32Array | Int32Array | Float32Array, |
||||||
|
T extends NonNullable<ReturnType<A['at']>>, |
||||||
|
>( |
||||||
|
l: L, |
||||||
|
m: Vector<D, A, T>, |
||||||
|
result: VectorBatch<L, D, A, T> = new VectorBatch(m._device, m._array, l, m.dims), |
||||||
|
): VectorBatch<L, D, A, T> { |
||||||
|
this.assertUnique(m, result); |
||||||
|
this.assertColocated(m, result); |
||||||
|
this.assertPackable(4, m); |
||||||
|
|
||||||
|
return result.calculate('unavg', SHADER_UNAVG, [m], false, 4); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,100 @@ |
|||||||
|
import { List, ListItem } from './list'; |
||||||
|
|
||||||
|
interface InternalQueueItem<T> { |
||||||
|
value: T; |
||||||
|
self?: ListItem<InternalQueueItem<T>>; |
||||||
|
orphan?: boolean; |
||||||
|
} |
||||||
|
|
||||||
|
export interface QueueItem<T> { |
||||||
|
readonly value: T; |
||||||
|
readonly self: ListItem<QueueItem<T>>; |
||||||
|
readonly orphan?: boolean; |
||||||
|
} |
||||||
|
|
||||||
|
export abstract class GroupQueueBase<K, T> { |
||||||
|
protected _groups: Map<K, List<InternalQueueItem<T>>> = new Map(); |
||||||
|
|
||||||
|
protected abstract hash(v: T): K; |
||||||
|
|
||||||
|
public get length(): number { |
||||||
|
return this._groups.size; |
||||||
|
} |
||||||
|
|
||||||
|
public constructor(values?: Iterable<T>) { |
||||||
|
for (const v of values ?? []) { |
||||||
|
this.insert(v); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public insert(value: T): QueueItem<T> { |
||||||
|
const hash: K = this.hash(value); |
||||||
|
const group: List<InternalQueueItem<T>> = this._groups.get(hash) ?? this._groups.set(hash, new List()).get(hash)!; |
||||||
|
const item: InternalQueueItem<T> = { value }; |
||||||
|
return (item.self = group.append(item)).value as QueueItem<T>; |
||||||
|
} |
||||||
|
|
||||||
|
public remove(item: QueueItem<T>): boolean { |
||||||
|
if (item.orphan) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
const hash: K = this.hash(item.value); |
||||||
|
const group: List<InternalQueueItem<T>> | undefined = this._groups.get(hash); |
||||||
|
if (!group) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
group.delete(item.self); |
||||||
|
if (!group.length) { |
||||||
|
this._groups.delete(hash); |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
public itemOf(value: T): QueueItem<T> | undefined { |
||||||
|
const hash: K = this.hash(value); |
||||||
|
const group: List<InternalQueueItem<T>> | undefined = this._groups.get(hash); |
||||||
|
if (!group) { |
||||||
|
return undefined; |
||||||
|
} |
||||||
|
for (const item of group) { |
||||||
|
if (item.value === value) { |
||||||
|
return item as QueueItem<T>; |
||||||
|
} |
||||||
|
} |
||||||
|
return undefined; |
||||||
|
} |
||||||
|
|
||||||
|
public peek(): List<QueueItem<T>> | undefined { |
||||||
|
if (!this._groups.size) { |
||||||
|
return undefined; |
||||||
|
} |
||||||
|
|
||||||
|
let group: List<InternalQueueItem<T>> | undefined = undefined; |
||||||
|
for (const g of this._groups.values()) { |
||||||
|
if (g.length > (group?.length ?? 0)) { |
||||||
|
group = g; |
||||||
|
} |
||||||
|
} |
||||||
|
return group as List<QueueItem<T>>; |
||||||
|
} |
||||||
|
|
||||||
|
public pop(): List<QueueItem<T>> | undefined { |
||||||
|
const result: List<QueueItem<T>> | undefined = this.peek(); |
||||||
|
for (const item of result ?? []) { |
||||||
|
this.remove(item); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
public *[Symbol.iterator](): IterableIterator<List<QueueItem<T>>> { |
||||||
|
for (const group of [...this._groups.values()].sort(({ length: ll }, { length: rl }) => rl - ll)) { |
||||||
|
yield group as List<QueueItem<T>>; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export class GroupQueue<T> extends GroupQueueBase<T, T> { |
||||||
|
protected override hash(v: T): T { |
||||||
|
return v; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,127 @@ |
|||||||
|
interface InternalListItem<T> { |
||||||
|
value: T; |
||||||
|
prev: InternalListItem<T>; |
||||||
|
next: InternalListItem<T>; |
||||||
|
orphan?: boolean; |
||||||
|
} |
||||||
|
|
||||||
|
export interface ListItem<T> { |
||||||
|
readonly value: T; |
||||||
|
readonly prev: ListItem<T>; |
||||||
|
readonly next: ListItem<T>; |
||||||
|
readonly orphan?: boolean; |
||||||
|
} |
||||||
|
|
||||||
|
export class List<T> implements Iterable<T> { |
||||||
|
protected _head?: InternalListItem<T>; |
||||||
|
protected _length: number = 0; |
||||||
|
|
||||||
|
public get first(): T | undefined { |
||||||
|
return this._head?.value; |
||||||
|
} |
||||||
|
|
||||||
|
public get last(): T | undefined { |
||||||
|
return this._head?.prev?.value; |
||||||
|
} |
||||||
|
|
||||||
|
public get length(): number { |
||||||
|
return this._length; |
||||||
|
} |
||||||
|
|
||||||
|
public constructor(values?: Iterable<T>) { |
||||||
|
for (const v of values ?? []) { |
||||||
|
this.append(v); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public prepend(value: T): ListItem<T> { |
||||||
|
if (!this._head) { |
||||||
|
this._head = { value } as InternalListItem<T>; |
||||||
|
this._head.prev = this._head.next = this._head; |
||||||
|
} else { |
||||||
|
this._head = { |
||||||
|
value, |
||||||
|
next: this._head, |
||||||
|
prev: this._head.prev, |
||||||
|
}; |
||||||
|
this._head.prev.next = this._head.next.prev = this._head; |
||||||
|
} |
||||||
|
this._length++; |
||||||
|
return this._head; |
||||||
|
} |
||||||
|
|
||||||
|
public append(value: T): ListItem<T> { |
||||||
|
this.prepend(value); |
||||||
|
this._head = this._head?.next; |
||||||
|
return this._head!.prev; |
||||||
|
} |
||||||
|
|
||||||
|
public includes(value: T): boolean { |
||||||
|
return !!this.itemOf(value); |
||||||
|
} |
||||||
|
|
||||||
|
public itemOf(value: T): ListItem<T> | undefined { |
||||||
|
for (const item of this.items()) { |
||||||
|
if (item.value === value) { |
||||||
|
return item; |
||||||
|
} |
||||||
|
} |
||||||
|
return undefined; |
||||||
|
} |
||||||
|
|
||||||
|
public set(item: ListItem<T>, value: T): boolean { |
||||||
|
if (item.orphan) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
(item as InternalListItem<T>).value = value; |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
public delete(item: ListItem<T>): boolean { |
||||||
|
if (item.orphan) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
if (this._head === item) { |
||||||
|
this._head = item.next; |
||||||
|
} |
||||||
|
|
||||||
|
if (this._head?.next === this._head) { |
||||||
|
this._head = undefined; |
||||||
|
} |
||||||
|
|
||||||
|
(item as InternalListItem<T>).prev.next = item.next; |
||||||
|
(item as InternalListItem<T>).next.prev = item.prev; |
||||||
|
(item as InternalListItem<T>).orphan = true; |
||||||
|
|
||||||
|
this._length--; |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
public *[Symbol.iterator](): IterableIterator<T> { |
||||||
|
for (const item of this.items()) { |
||||||
|
yield item.value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public values(): IterableIterator<T> { |
||||||
|
return this[Symbol.iterator](); |
||||||
|
} |
||||||
|
|
||||||
|
public *items(): IterableIterator<ListItem<T>> { |
||||||
|
if (!this._head) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
let head = this._head; |
||||||
|
let item = this._head; |
||||||
|
do { |
||||||
|
head = this._head; |
||||||
|
yield item; |
||||||
|
item = item.next; |
||||||
|
} while (item !== head); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,142 @@ |
|||||||
|
export type Vector<D extends number, I = number> = I[] & { length: D }; |
||||||
|
export type Matrix<R extends number, C extends number, I = number> = Vector<R, Vector<C, I>>; |
||||||
|
|
||||||
|
export function vec<D extends number, I = number>(s: D, f: (i: number) => I): Vector<D, I> { |
||||||
|
return Array.from({ length: s }, (_, i) => f(i)) as Vector<D, I>; |
||||||
|
} |
||||||
|
|
||||||
|
export function mat<R extends number, C extends number, I = number>(r: R, c: C, f: (x: number, y: number) => number): Matrix<R, C, I> { |
||||||
|
return Array.from({ length: r }, (_, y) => Array.from({ length: c }, (_, x) => f(x, y))) as Matrix<R, C, I>; |
||||||
|
} |
||||||
|
|
||||||
|
export function sum<R extends number, C extends number>(l: Matrix<R, C>, r: Matrix<R, C>): Matrix<R, C> { |
||||||
|
const result: Matrix<R, C> = mat(l.length, l[0].length, () => 0); |
||||||
|
for (let i = 0; i < l.length; i++) { |
||||||
|
for (let j = 0; j < r[0].length; j++) { |
||||||
|
result[i][j] += l[i][j] + r[i][j]; |
||||||
|
} |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
export function mul<R extends number, C extends number>(l: Matrix<R, C>, r: number): Matrix<R, C>; |
||||||
|
export function mul<D0 extends number, D1 extends number>(l: Vector<D0>, r: Matrix<D0, D1>): Vector<D1>; |
||||||
|
export function mul<R extends number, S extends number, C extends number>(l: Matrix<R, S>, r: Matrix<S, C>): Matrix<R, C>; |
||||||
|
export function mul(l: Matrix<number, number> | Vector<number>, r: Matrix<number, number> | number): Matrix<number, number> | Vector<number> { |
||||||
|
if (typeof r === 'number') { |
||||||
|
const lm: Matrix<number, number> = l as Matrix<number, number>; |
||||||
|
const result = Array.from({ length: lm.length }, () => Array(lm[0].length).fill(0)); |
||||||
|
for (let i = 0; i < lm.length; i++) { |
||||||
|
for (let j = 0; j < lm[i].length; j++) { |
||||||
|
result[i][j] += lm[i][j] * r; |
||||||
|
} |
||||||
|
} |
||||||
|
return result; |
||||||
|
} else if (!Array.isArray(l[0])) { |
||||||
|
const lv: Vector<number> = l as Vector<number>; |
||||||
|
const result = Array(r[0].length).fill(0); |
||||||
|
for (let j = 0; j < r[0].length; j++) { |
||||||
|
for (let i = 0; i < lv.length; i++) { |
||||||
|
result[j] += lv[i] * r[i][j]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} else { |
||||||
|
const lm: Matrix<number, number> = l as Matrix<number, number>; |
||||||
|
const result = Array.from({ length: lm.length }, () => Array(r[0].length).fill(0)); |
||||||
|
for (let i = 0; i < lm.length; i++) { |
||||||
|
for (let j = 0; j < r[0].length; j++) { |
||||||
|
for (let k = 0; k < r.length; k++) { |
||||||
|
result[i][j] += lm[i][k] * r[k][j]; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export function transpose<R extends number, C extends number>(m: Matrix<R, C>): Matrix<C, R> { |
||||||
|
return mat(m[0].length, m.length, (x, y) => m[x][y]); |
||||||
|
} |
||||||
|
|
||||||
|
export function softmax(row: number[]): number[] { |
||||||
|
const max = Math.max(...row); |
||||||
|
const exps = row.map(v => Math.exp(v - max)); |
||||||
|
const sum = exps.reduce((a, b) => a + b, 0); |
||||||
|
return exps.map(v => v / sum); |
||||||
|
} |
||||||
|
|
||||||
|
export function msoftmax<R extends number, C extends number>(m: Matrix<R, C>): Matrix<R, C> { |
||||||
|
return m.map(softmax) as Matrix<R, C>; |
||||||
|
} |
||||||
|
|
||||||
|
export function norm<R extends number, C extends number>(x: Matrix<R, C>, epsilon = 1e-5): Matrix<R, C> { |
||||||
|
return x.map(vec => { |
||||||
|
const mean = vec.reduce((sum, val) => sum + val, 0) / vec.length; |
||||||
|
const variance = vec.reduce((sum, val) => sum + (val - mean) ** 2, 0) / vec.length; |
||||||
|
return vec.map(val => (val - mean) / Math.sqrt(variance + epsilon)); |
||||||
|
}) as Matrix<R, C>; |
||||||
|
} |
||||||
|
|
||||||
|
export function relu<D extends number>(vec: Vector<D>): Vector<D> { |
||||||
|
return vec.map(v => Math.max(0, v)) as Vector<D>; |
||||||
|
} |
||||||
|
|
||||||
|
export function mean<D extends number>(v: Vector<D>): number { |
||||||
|
return v.reduce((s, c) => s + c, 0) / v.length; |
||||||
|
} |
||||||
|
|
||||||
|
export function drm(data: number[], interval = 0.9): number { |
||||||
|
const sorted = [...data].sort((a, b) => a - b); |
||||||
|
const count = Math.floor(data.length * interval); |
||||||
|
let mi = 0; |
||||||
|
let mv = Infinity; |
||||||
|
|
||||||
|
for (let i = 0; i <= data.length - count; i++) { |
||||||
|
const range = sorted[i + count - 1] - sorted[i]; |
||||||
|
if (range < mv) { |
||||||
|
mv = range; |
||||||
|
mi = i; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return sorted.slice(mi, mi + count).reduce((a, b) => a + b, 0) / count; |
||||||
|
} |
||||||
|
|
||||||
|
export function randseq(n: number): number[] { |
||||||
|
const is = Array.from({ length: n }, (_, i) => i); |
||||||
|
for (let i = n - 1; i > 0; i--) { |
||||||
|
const j = Math.floor(Math.random() * (i + 1)); |
||||||
|
[is[i], is[j]] = [is[j], is[i]]; // swap
|
||||||
|
} |
||||||
|
return is; |
||||||
|
} |
||||||
|
|
||||||
|
export function stddev(xs: number[]): number { |
||||||
|
const m = mean(xs); |
||||||
|
return Math.sqrt(xs.reduce((s, v) => s + (v - m) ** 2, 0) / xs.length); |
||||||
|
} |
||||||
|
|
||||||
|
export function ema(xs: number[], alpha = 0.1): number { |
||||||
|
let result = xs[0]; |
||||||
|
for (let i = 1; i < xs.length; i++) { |
||||||
|
result = alpha * xs[i] + (1 - alpha) * result; |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
export function slope(xs: number[]): number { |
||||||
|
const xMean = (xs.length - 1) / 2; |
||||||
|
const yMean = mean(xs); |
||||||
|
|
||||||
|
let num = 0, den = 0; |
||||||
|
for (let i = 0; i < xs.length; i++) { |
||||||
|
const x = i - xMean; |
||||||
|
const y = xs[i] - yMean; |
||||||
|
num += x * y; |
||||||
|
den += x * x; |
||||||
|
} |
||||||
|
|
||||||
|
return num / den; |
||||||
|
} |
||||||
@ -0,0 +1,73 @@ |
|||||||
|
export interface InternalTrieNode { |
||||||
|
children: Map<string, InternalTrieNode>; |
||||||
|
token?: string; |
||||||
|
} |
||||||
|
|
||||||
|
export interface TrieNode { |
||||||
|
readonly children: Map<string, TrieNode>; |
||||||
|
readonly token?: string; |
||||||
|
} |
||||||
|
|
||||||
|
export class Trie implements Iterable<string> { |
||||||
|
protected root: InternalTrieNode = { children: new Map() }; |
||||||
|
|
||||||
|
constructor(values?: string[]) { |
||||||
|
for (const value of values ?? []) { |
||||||
|
this.insert(value); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public insert(value: string) { |
||||||
|
let node = this.root; |
||||||
|
for (const char of value) { |
||||||
|
node = node.children.get(char) ?? node.children.set(char, { children: new Map() }).get(char)!; |
||||||
|
} |
||||||
|
node.token = value; |
||||||
|
} |
||||||
|
|
||||||
|
public match(value: string): string | undefined { |
||||||
|
let node: TrieNode = this.root; |
||||||
|
let match: string | undefined = undefined; |
||||||
|
for (let i = 0; i < value.length && node.children.has(value[i]); i++) { |
||||||
|
match = (node = node.children.get(value[i])!).token ?? match; |
||||||
|
} |
||||||
|
return match; |
||||||
|
} |
||||||
|
|
||||||
|
public *[Symbol.iterator](): IterableIterator<string> { |
||||||
|
const queue: InternalTrieNode[] = []; |
||||||
|
let item: InternalTrieNode | undefined = this.root; |
||||||
|
while (item) { |
||||||
|
if (item.token) { |
||||||
|
yield item.token; |
||||||
|
} |
||||||
|
queue.push(...item.children.values()); |
||||||
|
item = queue.pop(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public toJSON(node: InternalTrieNode = this.root): object { |
||||||
|
return [ |
||||||
|
...node.token ? [node.token] : [], |
||||||
|
...[...node.children.entries()].map(([k, c]) => [k, this.toJSON(c)]), |
||||||
|
]; |
||||||
|
} |
||||||
|
|
||||||
|
protected static nodeFromJSON(element: object): InternalTrieNode { |
||||||
|
if (!Array.isArray(element) || !element.every(e => typeof e === 'string' |
||||||
|
|| Array.isArray(e) && e.length === 2)) { |
||||||
|
throw new Error('Malformed Trie JSON.'); |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
children: new Map((typeof element[0] === 'string' ? element.slice(1) : element).map(([k, v]) => [k, this.nodeFromJSON(v)])), |
||||||
|
token: typeof element[0] === 'string' ? element[0] : undefined, |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
public static fromJSON(json: object): Trie { |
||||||
|
const result = new Trie(); |
||||||
|
result.root = Trie.nodeFromJSON(json); |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
||||||
@ -1,72 +0,0 @@ |
|||||||
import { writeFileSync } from 'node:fs'; |
|
||||||
|
|
||||||
let indent: string = ''; |
|
||||||
const logs: string[] = []; |
|
||||||
|
|
||||||
let logging: boolean = false; |
|
||||||
export function log(): void { |
|
||||||
logging = true; |
|
||||||
} |
|
||||||
|
|
||||||
export function withLog<T>(before: string, f: () => T, after: (v: T) => string): T { |
|
||||||
if (!logging) { |
|
||||||
return f(); |
|
||||||
} |
|
||||||
|
|
||||||
logs.push(`${indent}${before}`); |
|
||||||
const l = logs.length; |
|
||||||
indent += ' '; |
|
||||||
const result = f(); |
|
||||||
indent = indent.slice(2); |
|
||||||
if (logs.length === l) { |
|
||||||
logs[logs.length - 1] += ` ${after(result)}`; |
|
||||||
} else { |
|
||||||
logs.push(`${indent}${after(result)}`); |
|
||||||
} |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
let profiling: boolean = false; |
|
||||||
export function profile(): void { |
|
||||||
profiling = true; |
|
||||||
} |
|
||||||
|
|
||||||
let index = 0; |
|
||||||
export function withProfile<T>(name: string, detail: Record<'cat', string> & Record<string, unknown>, f: () => T) { |
|
||||||
if (!logging) { |
|
||||||
return f(); |
|
||||||
} |
|
||||||
|
|
||||||
const i = index++; |
|
||||||
performance.mark(`${name}::${i}::start`, { detail }); |
|
||||||
const result = f(); |
|
||||||
performance.mark(`${name}::${i}::end`, { detail: { ...detail, result: `${result}` } }); |
|
||||||
performance.measure(name, { |
|
||||||
start: `${name}::${i}::start`, |
|
||||||
end: `${name}::${i}::end`, |
|
||||||
detail: { ...detail, result: `${result}` }, |
|
||||||
}); |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
export function dump() { |
|
||||||
if (logging) { |
|
||||||
console.log('\n\nExecution log:'); |
|
||||||
console.log(logs.join('\n')); |
|
||||||
} |
|
||||||
if (profiling) { |
|
||||||
writeFileSync('profile.json', JSON.stringify({ |
|
||||||
pid: 0, |
|
||||||
tid: 0, |
|
||||||
displayTimeUnit: 'ns', |
|
||||||
traceEvents: performance.getEntriesByType('measure').map(({ name, startTime, duration, detail }) => ({ |
|
||||||
name, |
|
||||||
cat: (detail as { cat: string })?.cat, |
|
||||||
ph: 'X', |
|
||||||
ts: startTime * 1000, |
|
||||||
dur: duration * 1000, |
|
||||||
args: detail, |
|
||||||
})), |
|
||||||
})); |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,14 +1,196 @@ |
|||||||
import { dump, log, profile } from './debug'; |
import fs from 'node:fs'; |
||||||
import { _ } from './core'; |
import { create, globals } from 'webgpu'; |
||||||
import { fromNumber, mul, toNumber } from './byte'; |
import yargs from 'yargs'; |
||||||
|
import { Matrix } from './data/gpu/matrix'; |
||||||
|
import { Scalar } from './data/gpu/scalar'; |
||||||
|
import { mul, sum, transpose } from './data/math'; |
||||||
|
import { Model } from './model-gpu'; |
||||||
|
import { Tokenizer } from './tokenizer'; |
||||||
|
|
||||||
log(); |
Object.assign(globalThis, globals); |
||||||
profile(); |
|
||||||
|
|
||||||
const program = mul._(fromNumber(1))._(fromNumber(10)); |
const GPU = create([]); |
||||||
|
|
||||||
try { |
const device = await GPU.requestAdapter({ powerPreference: 'high-performance' }).then(adapter => adapter?.requestDevice()); |
||||||
console.log(program.stringify(), ' -> ', toNumber(program)); |
|
||||||
} finally { |
if (!device) { |
||||||
dump(); |
process.stderr.write('Can not find device.\n'); |
||||||
|
process.exit(1); |
||||||
|
} |
||||||
|
process.stderr.write(`Running on: ${device.adapterInfo.description}.\n`); |
||||||
|
|
||||||
|
const args = await yargs() |
||||||
|
.option('model', { type: 'string', demandOption: true }) |
||||||
|
.command('test-matrix', 'Test core components') |
||||||
|
.command('train-tokenizer', 'Train new tokenizer', yargs => yargs |
||||||
|
.option('vocab', { type: 'number', demandOption: true })) |
||||||
|
.command('tokenize', 'Tokenize input using existing tokenizer', yargs => yargs |
||||||
|
.option('input', { type: 'string' }) |
||||||
|
.option('output', { type: 'string' })) |
||||||
|
.command('configure', 'Configure new model', yargs => yargs |
||||||
|
.option('window', { type: 'number', demandOption: true }) |
||||||
|
.option('dimensions', { type: 'number', demandOption: true }) |
||||||
|
.option('heads', { type: 'number', demandOption: true }) |
||||||
|
.option('ffs', { type: 'number', demandOption: true })) |
||||||
|
.command('train', 'Train existing model', yargs => yargs |
||||||
|
.option('epochs', { type: 'number' }) |
||||||
|
.option('stats', { type: 'string' }) |
||||||
|
.option('logits', { type: 'string' }) |
||||||
|
.option('samples', { type: 'string' })) |
||||||
|
.command('transform', 'Run single step of existing model', yargs => yargs |
||||||
|
.option('input', { type: 'string' }) |
||||||
|
.option('output', { type: 'string' }) |
||||||
|
.option('temperature', { type: 'number' }) |
||||||
|
.option('logits', { type: 'string' })) |
||||||
|
.command('run', 'Run existing model', yargs => yargs |
||||||
|
.option('input', { type: 'string' }) |
||||||
|
.option('output', { type: 'string' }) |
||||||
|
.option('temperature', { type: 'number' }) |
||||||
|
.option('samples', { type: 'string' }) |
||||||
|
.option('logits', { type: 'string' })) |
||||||
|
.command('benchmark', 'Benchmark existing model') |
||||||
|
.demandCommand(1) |
||||||
|
.parse(process.argv.slice(2)); |
||||||
|
|
||||||
|
if (args._.includes('test-matrix')) { |
||||||
|
(Matrix as any).unit = 1; |
||||||
|
|
||||||
|
const SL = new Matrix(device, Float32Array, 2, 4, [ |
||||||
|
[3, -7, 6, 12], |
||||||
|
[11, -5, 7, 3], |
||||||
|
]); |
||||||
|
const SR = new Matrix(device, Float32Array, 2, 4, [ |
||||||
|
[6, 12, 3, -7], |
||||||
|
[7, 3, 11, -5], |
||||||
|
]); |
||||||
|
|
||||||
|
const SO = new Matrix(device, Float32Array, 2, 4); |
||||||
|
|
||||||
|
const SS = new Scalar(device, Float32Array, 3); |
||||||
|
|
||||||
|
const ML = new Matrix(device, Float32Array, 2, 6, [ |
||||||
|
[3, -7, 6, 12, 4, 2], |
||||||
|
[11, -5, 7, 3, 2, 4], |
||||||
|
]); |
||||||
|
const MR = new Matrix(device, Float32Array, 6, 2, [ |
||||||
|
[6, 12], |
||||||
|
[3, -7], |
||||||
|
[7, 3], |
||||||
|
[11, -5], |
||||||
|
[4, 2], |
||||||
|
[2, 4], |
||||||
|
]); |
||||||
|
|
||||||
|
const MO = new Matrix(device, Float32Array, 2, 2); |
||||||
|
|
||||||
|
console.log('Sum: CPU: ', sum(await SL.get(), await SR.get())); |
||||||
|
console.log('Sum: GPU (new): ', await Matrix.sum(SL, SR, SO).get()); |
||||||
|
console.log('Sum: GPU (in-place): ', await Matrix.sum(SL, SR, SR).get()); |
||||||
|
|
||||||
|
console.log('Scale: CPU: ', mul(await SL.get(), 3)); |
||||||
|
console.log('Scale: GPU (new): ', await Matrix.scale(SS, SL, SR).get()); |
||||||
|
console.log('Scale: GPU (in-place): ', await Matrix.scale(SS, SL, SL).get()); |
||||||
|
|
||||||
|
console.log('Mul: CPU: ', mul(await ML.get(), await MR.get())); |
||||||
|
console.log('Mul: GPU: ', await Matrix.mul(ML, MR, MO).get()); |
||||||
|
|
||||||
|
console.log('Transpose: CPU: ', transpose(await ML.get())); |
||||||
|
console.log('Transpose: GPU: ', await Matrix.transpose(ML, MR).get()); |
||||||
|
} |
||||||
|
|
||||||
|
const dir: string = args.model; |
||||||
|
|
||||||
|
let tokenizer: Tokenizer | undefined; |
||||||
|
let model: Model | undefined; |
||||||
|
if (args._.includes('train-tokenizer')) { |
||||||
|
tokenizer = new Tokenizer(args['vocab'] as number, fs.readFileSync(`${dir}/corpus.txt`).toString()); |
||||||
|
|
||||||
|
fs.writeFileSync(`${dir}/vocab.json`, JSON.stringify(tokenizer.toJSON())); |
||||||
|
fs.writeFileSync(`${dir}/vocab-flat.txt`, tokenizer.tokens.map(s => JSON.stringify(s).slice(1, -1)).join('\n')); |
||||||
|
} |
||||||
|
|
||||||
|
if (args._.includes('tokenize') || args._.includes('configure') || args._.includes('train') || args._.includes('transform') || args._.includes('run') || args._.includes('benchmark')) { |
||||||
|
if (!tokenizer && !fs.existsSync(`${dir}/vocab.json`)) { |
||||||
|
process.stderr.write('Can not find tokenizer data.\n'); |
||||||
|
process.exit(1); |
||||||
|
} |
||||||
|
|
||||||
|
tokenizer ??= new Tokenizer(JSON.parse(fs.readFileSync(`${dir}/vocab.json`).toString())); |
||||||
|
|
||||||
|
if (args._.includes('tokenize')) { |
||||||
|
fs.writeFileSync(args['output'] as string || 1, tokenizer.detokenize(tokenizer.tokenize(fs.readFileSync(args['input'] as string || 0).toString())).map(s => JSON.stringify(s).slice(1, -1)).join('\n')); |
||||||
} |
} |
||||||
|
|
||||||
|
if (args._.includes('configure')) { |
||||||
|
model = new Model(device, tokenizer, { |
||||||
|
tokenizer: `${tokenizer.tokens.length}${tokenizer.tokens[0]}`, |
||||||
|
vs: tokenizer.tokens.length, |
||||||
|
ws: args['window'] as number, |
||||||
|
ds: args['dimensions'] as number, |
||||||
|
}); |
||||||
|
fs.writeFileSync(`${dir}/model.json`, JSON.stringify(await model.toJSON())); |
||||||
|
|
||||||
|
if (args['stats']) { fs.writeFileSync(args['stats'] as string, 'index,timestamp,epoch,batch,lr,loss,accuracy,grad_inp,grad_qry,grad_key,grad_val,grad_out,queueing,computing,inferring\n'); } |
||||||
|
if (args['logits']) { fs.writeFileSync(args['logits'] as string, 'sample,token,itid,ttid,t0,o0,p0,t1,o1,p1,t2,o2,p2,tNm2,oNm2,pNm2,tNm1,oNm1,pNm1,tN,oN,pN,sum,stddev,entropy,loss\n'); } |
||||||
|
if (args['samples']) { fs.writeFileSync(args['samples'] as string, ''); } |
||||||
|
} |
||||||
|
|
||||||
|
if (args._.includes('train') || args._.includes('transform') || args._.includes('run') || args._.includes('benchmark')) { |
||||||
|
if (!model && !fs.existsSync(`${dir}/model.json`)) { |
||||||
|
process.stderr.write('Can not find model data.\n'); |
||||||
|
process.exit(1); |
||||||
|
} |
||||||
|
|
||||||
|
model ??= new Model(device, tokenizer, JSON.parse(fs.readFileSync(`${dir}/model.json`).toString())); |
||||||
|
|
||||||
|
if (args._.includes('train')) { |
||||||
|
const snapshot = (json: object) => fs.writeFileSync(`${dir}/model.json`, JSON.stringify(json)); |
||||||
|
|
||||||
|
await model.train( |
||||||
|
args.epochs as number || 'auto', |
||||||
|
fs.readFileSync(`${dir}/corpus.txt`).toString(), |
||||||
|
{ |
||||||
|
logs: process.stderr, |
||||||
|
stats: args['stats'] ? fs.createWriteStream(args['stats'] as string, { flags: 'a' }) : undefined, |
||||||
|
logits: args['logits'] ? fs.createWriteStream(args['logits'] as string, { flags: 'a' }) : undefined, |
||||||
|
samples: args['samples'] ? fs.createWriteStream(args['samples'] as string, { flags: 'a' }) : undefined, |
||||||
|
snapshot, |
||||||
|
}); |
||||||
|
|
||||||
|
snapshot(await model.toJSON()); |
||||||
|
} |
||||||
|
|
||||||
|
if (args._.includes('transform')) { |
||||||
|
const [inp, out] = await model.transform( |
||||||
|
tokenizer.tokenize(fs.readFileSync(args['input'] as string || 0).toString()), |
||||||
|
{ |
||||||
|
temp: args['temperature'] as number || 1, |
||||||
|
logits: args['logits'] ? fs.createWriteStream(args['logits'] as string) : undefined, |
||||||
|
}, |
||||||
|
); |
||||||
|
|
||||||
|
fs.writeFileSync(args['output'] as string || 1, `Input:\n${tokenizer.detokenize(inp).join('')}\nOutput:${tokenizer.detokenize(out).join('\n')}\n`); |
||||||
|
} |
||||||
|
|
||||||
|
if (args._.includes('run')) { |
||||||
|
fs.writeFileSync(args['output'] as string || 1, tokenizer.detokenize(await model.run( |
||||||
|
tokenizer.tokenize(fs.readFileSync(args['input'] as string || 0).toString()), |
||||||
|
{ |
||||||
|
temp: args['temperature'] as number || 1, |
||||||
|
logits: args['logits'] ? fs.createWriteStream(args['logits'] as string) : undefined, |
||||||
|
samples: args['samples'] as number || model.ws, |
||||||
|
}, |
||||||
|
)).join('') + '\n'); |
||||||
|
} |
||||||
|
|
||||||
|
if (args._.includes('benchmark')) { |
||||||
|
throw new Error('Not implemented.'); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
await device.queue.onSubmittedWorkDone(); |
||||||
|
|
||||||
|
device.destroy(); |
||||||
|
|
||||||
|
process.stderr.write('All done!\n'); |
||||||
|
|||||||
@ -1,7 +0,0 @@ |
|||||||
import { _, _l, g, n } from './core'; |
|
||||||
import { concat, intersperse, map } from './list'; |
|
||||||
import { fromString as _s, cons, snoc } from './string'; |
|
||||||
|
|
||||||
n('list.show'); |
|
||||||
|
|
||||||
export const show = g('show', _l(eshow => _l(xs => concat._(snoc._(cons._(_s('['))._(intersperse._(_s(', '))._(map._(eshow)._(xs))))._(_s(']')))))); |
|
||||||
@ -1,72 +0,0 @@ |
|||||||
import { $, Term, _, _l, cnst, g, id, n, pipe, undef } from './core'; |
|
||||||
import { EQ, GT, LT, eq as eqc } from './ord'; |
|
||||||
import { False, True, and, iif, or } from './bool'; |
|
||||||
import { Zero, succ } from './num'; |
|
||||||
|
|
||||||
n('list'); |
|
||||||
|
|
||||||
export const Nil = g('Nil', _l(z => _l(_ => z))); |
|
||||||
|
|
||||||
export const Cons = g('Cons', _l(x => _l(xs => _l(_ => _l(f => f._(x)._(xs)))))); |
|
||||||
|
|
||||||
export const cons = g('cons', Cons); |
|
||||||
|
|
||||||
export const snoc = g('snoc', _l(xs => _l(x => xs._(cons._(x)._(Nil))._(_l(y => _l(ys => cons._(y)._(snoc._(ys)._(x)))))))); |
|
||||||
|
|
||||||
export const nul = g('nul', _l(xs => xs._(True)._(cnst._(cnst._(False))))); |
|
||||||
|
|
||||||
export const len = g('len', _l(xs => xs._(Zero)._(cnst._(pipe._(succ)._(len))))); |
|
||||||
|
|
||||||
export const singleton = g('singleton', _l(x => cons._(x)._(Nil))); |
|
||||||
|
|
||||||
export const append = g('append', _l(l => _l(r => l._(r)._(_l(x => _l(xs => cons._(x)._(append._(xs)._(r)))))))); |
|
||||||
|
|
||||||
export const concat = g('concat', _l(ls => ls._(Nil)._(_l(x => pipe._(append._(x))._(concat))))); |
|
||||||
|
|
||||||
export const repeat = g('repeat', _l(x => cons._(x)._(repeat._(x)))); |
|
||||||
|
|
||||||
export const iterate = g('iterate', _l(f => _l(x => cons._(x)._(iterate._(f)._(f._(x)))))); |
|
||||||
|
|
||||||
export const head = g('head', _l(xs => xs._(undef)._(cnst))); |
|
||||||
|
|
||||||
export const tail = g('tail', _l(xs => xs._(undef)._(cnst._(id)))); |
|
||||||
|
|
||||||
export const foldl = g('foldl', _l(f => _l(z => _l(xs => xs._(z)._(pipe._(foldl._(f))._(f._(z))))))); |
|
||||||
|
|
||||||
export const foldlz = g('foldlz', _l(f => _l(xs => xs._(undef)._(foldl._(f))))); |
|
||||||
|
|
||||||
export const foldr = g('foldr', _l(f => _l(z => _l(xs => xs._(z)._(_l(x => pipe._(f._(x))._(foldr._(f)._(z)))))))); |
|
||||||
|
|
||||||
export const foldrz = g('foldrz', undef); |
|
||||||
|
|
||||||
export const cmp = g('cmp', _l(ecmp => _l(l => _l(r => l._(r._(EQ)._(cnst._(cnst._(LT))))._(_l(lx => _l(lxs => r._(GT)._(_l(rx => _l(rxs => iif._(eqc._(EQ)._(ecmp._(lx)._(rx)))._(cmp._(ecmp)._(lxs)._(rxs))._(ecmp._(lx)._(rx)))))))))))); |
|
||||||
|
|
||||||
export const lt = g('lt', _l(ecmp => _l(l => _l(r => r._(False)._(_l(rx => _l(rxs => l._(True)._(_l(lx => _l(lxs => ecmp._(lx)._(rx)._(True)._(lt._(ecmp)._(lxs)._(rxs))._(False))))))))))); |
|
||||||
|
|
||||||
export const le = g('le', _l(ecmp => _l(l => _l(r => l._(True)._(_l(lx => _l(lxs => r._(False)._(_l(rx => _l(rxs => ecmp._(lx)._(rx)._(True)._(le._(ecmp)._(lxs)._(rxs))._(False))))))))))); |
|
||||||
|
|
||||||
export const eq = g('eq', _l(eeq => _l(l => _l(r => l._(r._(True)._(cnst._(cnst._(False))))._(_l(lx => _l(lxs => r._(False)._(_l(rx => _l(rxs => and._(eeq._(lx)._(rx))._(eq._(eeq)._(lxs)._(rxs)))))))))))); |
|
||||||
|
|
||||||
export const ge = g('ge', _l(ecmp => _l(l => _l(r => r._(True)._(_l(rx => _l(rxs => l._(False)._(_l(lx => _l(lxs => ecmp._(lx)._(rx)._(False)._(ge._(ecmp)._(lxs)._(rxs))._(True))))))))))); |
|
||||||
|
|
||||||
export const gt = g('gt', _l(ecmp => _l(l => _l(r => l._(False)._(_l(lx => _l(lxs => r._(True)._(_l(rx => _l(rxs => ecmp._(lx)._(rx)._(False)._(gt._(ecmp)._(lxs)._(rxs))._(True))))))))))); |
|
||||||
|
|
||||||
export const map = g('map', _l(f => _l(xs => xs._(Nil)._(_l(x => pipe._(cons._(f._(x)))._(map._(f))))))); |
|
||||||
|
|
||||||
export const filter = g('filter', _l(f => _l(xs => xs._(Nil)._(_l(x => pipe._(iif._(f._(x))._(cons._(x))._(id))._(filter._(f))))))); |
|
||||||
|
|
||||||
export const any = g('any', _l(f => _l(xs => xs._(False)._(_l(x => pipe._(or._(f._(x)))._(any._(f))))))); |
|
||||||
|
|
||||||
export const all = g('all', _l(f => _l(xs => xs._(True)._(_l(x => pipe._(and._(f._(x)))._(all._(f))))))); |
|
||||||
|
|
||||||
export const get = g('get', _l(i => _l(xs => xs._(undef)._(_l(x => i._(cnst._(x))._(get)))))); |
|
||||||
|
|
||||||
export const update = g('update', _l(f => _l(i => _l(xs => xs._(undef)._(_l(x => _l(xs => i._(cons._(f._(x))._(xs))._(_l(pi => cons._(x)._(update._(f)._(pi)._(xs))))))))))); |
|
||||||
|
|
||||||
export const set = g('set', pipe._(update)._(cnst)); |
|
||||||
|
|
||||||
export const intersperse = g('intersperse', _l(v => _l(xs => xs._(xs)._(_l(x => _l(rxs => rxs._(xs)._(cnst._(cnst._(cons._(x)._(cons._(v)._(intersperse._(v)._(rxs)))))))))))); |
|
||||||
|
|
||||||
export const fromArray: (xs: Term[]) => Term = xs => !xs.length ? Nil : Cons._(xs[0])._(_(() => fromArray(xs.slice(1)))); |
|
||||||
|
|
||||||
export const toArray: (xs: Term) => Term[] = xs => $(xs._(_([]))._(_l(x => _l(xs => _(({ x, xs }) => _([x, ...toArray(xs)])))))); |
|
||||||
@ -1,7 +0,0 @@ |
|||||||
import { _, _l } from './core'; |
|
||||||
import { toString } from './string'; |
|
||||||
|
|
||||||
export const log = _l(s => _l(p => _(({ s, p }) => { |
|
||||||
console.log(toString(s)); |
|
||||||
return p; |
|
||||||
}))); |
|
||||||
@ -0,0 +1,261 @@ |
|||||||
|
import { Matrix, Vector, mat, mul, softmax, sum, transpose } from './data/math'; |
||||||
|
import { TID } from './tokenizer'; |
||||||
|
|
||||||
|
export interface ModelInitData< |
||||||
|
V extends number, |
||||||
|
W extends number, |
||||||
|
D extends number, |
||||||
|
H extends number, |
||||||
|
F extends number, |
||||||
|
> { |
||||||
|
readonly vs: V; |
||||||
|
readonly ws: W; |
||||||
|
readonly ds: D; |
||||||
|
readonly hs: H; |
||||||
|
readonly fs: F; |
||||||
|
} |
||||||
|
|
||||||
|
export interface ModelData< |
||||||
|
V extends number, |
||||||
|
W extends number, |
||||||
|
D extends number, |
||||||
|
H extends number, |
||||||
|
HD extends number, |
||||||
|
F extends number, |
||||||
|
FD extends number, |
||||||
|
> extends ModelInitData<V, W, D, H, F> { |
||||||
|
readonly trained: true; |
||||||
|
readonly positions: Matrix<W, D>; |
||||||
|
readonly embeddings: Matrix<V, D>; |
||||||
|
readonly weights: { |
||||||
|
query: Matrix<D, HD>; |
||||||
|
key: Matrix<D, HD>; |
||||||
|
value: Matrix<D, HD>; |
||||||
|
attn: Matrix<D, D>; |
||||||
|
ffi: Matrix<D, FD>; |
||||||
|
ffo: Matrix<FD, D>; |
||||||
|
out: Matrix<D, V>; |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
const ES: number = 1; |
||||||
|
|
||||||
|
export class Model< |
||||||
|
V extends number = number, |
||||||
|
W extends number = number, |
||||||
|
D extends number = number, |
||||||
|
H extends number = number, |
||||||
|
HD extends number = number, |
||||||
|
F extends number = number, |
||||||
|
FD extends number = number, |
||||||
|
> { |
||||||
|
protected readonly vs: V; |
||||||
|
protected readonly ws: W; |
||||||
|
protected readonly ds: D; |
||||||
|
protected readonly hs: H; |
||||||
|
protected readonly hds: HD; |
||||||
|
protected readonly fs: F; |
||||||
|
protected readonly fds: FD; |
||||||
|
|
||||||
|
protected positions: Matrix<W, D>; |
||||||
|
protected embeddings: Matrix<V, D>; |
||||||
|
|
||||||
|
protected readonly weights: { |
||||||
|
query: Matrix<D, HD>; |
||||||
|
key: Matrix<D, HD>; |
||||||
|
value: Matrix<D, HD>; |
||||||
|
attn: Matrix<D, D>; |
||||||
|
ffi: Matrix<D, FD>; |
||||||
|
ffo: Matrix<FD, D>; |
||||||
|
out: Matrix<D, V>; |
||||||
|
}; |
||||||
|
|
||||||
|
protected embed(tids: Vector<W, TID>): Matrix<W, D> { |
||||||
|
return sum(tids.map(tid => this.embeddings[tid]) as Matrix<W, D>, this.positions); |
||||||
|
} |
||||||
|
|
||||||
|
// protected attentize<N extends number>(input: Matrix<N, D>): Matrix<N, D> {
|
||||||
|
// const SCALE: number = 1 / Math.sqrt(this.hds);
|
||||||
|
// const Q: Matrix<N, HD> = mul(input, this.weights.query);
|
||||||
|
// const K: Matrix<N, HD> = mul(input, this.weights.key);
|
||||||
|
// const V: Matrix<N, HD> = mul(input, this.weights.value);
|
||||||
|
// const scores: Matrix<N, N> = msoftmax(mul(mul(Q, transpose(K)), SCALE));
|
||||||
|
// const out: Matrix<N, HD> = mul(scores, V);
|
||||||
|
// return mul(out as unknown as Matrix<N, D>, this.weights.attn);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// protected feedforward<N extends number>(input: Matrix<N, D>): Matrix<N, D> {
|
||||||
|
// return input.map((v: Vector<D>) => mul(relu(mul(v, this.weights.ffi)), this.weights.ffo)) as Matrix<N, D>;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// protected transform<N extends number>(input: Matrix<N, D>): Matrix<N, D> {
|
||||||
|
// const attned: Matrix<N, D> = sum(input, this.attentize(input));
|
||||||
|
// const ffned: Matrix<N, D> = sum(input, this.feedforward(attned));
|
||||||
|
// return ffned;
|
||||||
|
// }
|
||||||
|
|
||||||
|
protected logitize(input: Matrix<W, D>): Matrix<W, V> { |
||||||
|
return mul(input, this.weights.out).map(softmax) as Matrix<W, V>; |
||||||
|
} |
||||||
|
|
||||||
|
protected argmax(logit: Vector<V>): TID { |
||||||
|
return logit.reduce((mi, p, i, a) => p > a[mi] ? i : mi, 0); |
||||||
|
} |
||||||
|
|
||||||
|
protected sample(logit: Vector<V>): TID { |
||||||
|
let p: number = 0; |
||||||
|
const t: number = Math.random(); |
||||||
|
for (let i = 0; i < logit.length; i++) { |
||||||
|
p += logit[i]; |
||||||
|
if (t < p) { |
||||||
|
return i; |
||||||
|
} |
||||||
|
} |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
protected forward(tids: Vector<W, TID>): Matrix<W, V> { |
||||||
|
return this.logitize(this.embed(tids)); |
||||||
|
} |
||||||
|
|
||||||
|
protected loss<N extends number>(probs: Vector<N, Vector<V>>, target: Vector<N, TID>): number { |
||||||
|
return probs.reduce((l: number, ps: Vector<V>, i: number) => l - Math.log(ps[target[i]] + 1e-10), 0) / probs.length; |
||||||
|
} |
||||||
|
|
||||||
|
protected grad(probs: Matrix<W, V>, target: Vector<W, TID>): Matrix<W, V> { |
||||||
|
const grads = mul(probs, 1); |
||||||
|
target.forEach((t, i) => grads[i][t] -= 1); |
||||||
|
return grads; |
||||||
|
} |
||||||
|
|
||||||
|
protected updateWOut<N extends number>(input: Matrix<N, D>, grad: Matrix<N, V>, lr: number): void { |
||||||
|
this.weights.out = sum(mul(mul(transpose(input), grad), -lr), this.weights.out); |
||||||
|
} |
||||||
|
|
||||||
|
public constructor(_device: unknown, data: ModelInitData<V, W, D, H, F> | ModelData<V, W, D, H, HD, F, FD>) { |
||||||
|
this.vs = data.vs; |
||||||
|
this.ws = data.ws; |
||||||
|
this.ds = data.ds; |
||||||
|
this.hs = data.hs; |
||||||
|
this.hds = (this.ds / this.hs) as HD; |
||||||
|
this.fs = data.fs; |
||||||
|
this.fds = (this.ds * this.fs) as FD; |
||||||
|
if (!('trained' in data)) { |
||||||
|
this.positions = mat(this.ws, this.ds, (d, i) => { |
||||||
|
const angle = i / Math.pow(10000, 2 * Math.floor(d / 2) / this.ds); |
||||||
|
return d % 2 === 0 ? Math.sin(angle) * ES : Math.cos(angle) * ES; |
||||||
|
}); |
||||||
|
this.embeddings = mat(this.vs, this.ds, () => Math.random() * 2 * ES - ES); |
||||||
|
this.weights = { |
||||||
|
query: mat(this.ds, this.hds, () => Math.random() * 0.02 - 0.01), |
||||||
|
key: mat(this.ds, this.hds, () => Math.random() * 0.02 - 0.01), |
||||||
|
value: mat(this.ds, this.hds, () => Math.random() * 0.02 - 0.01), |
||||||
|
attn: mat(this.ds, this.ds, () => Math.random() * 0.02 - 0.01), |
||||||
|
ffi: mat(this.ds, this.fds, () => Math.random() * 0.02 - 0.01), |
||||||
|
ffo: mat(this.fds, this.ds, () => Math.random() * 0.02 - 0.01), |
||||||
|
out: mat(this.ds, this.vs, () => Math.random() * 0.2 - 0.1), |
||||||
|
}; |
||||||
|
} else { |
||||||
|
this.positions = data.positions; |
||||||
|
this.embeddings = data.embeddings; |
||||||
|
this.weights = data.weights; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public train(data: TID[], epochs: number): void { |
||||||
|
let log = Date.now(); |
||||||
|
const samples: number[] = []; |
||||||
|
console.log('Training model.'); |
||||||
|
|
||||||
|
const runs = data.length - this.ws - 1; |
||||||
|
for (let epoch = 0; epoch < epochs; epoch++) { |
||||||
|
for (let s = 0; s < runs; s++) { |
||||||
|
const begin: number = Date.now(); |
||||||
|
|
||||||
|
const source = data.slice(s, s + this.ws) as Vector<W>; |
||||||
|
const target = data.slice(s + 1, s + 1 + this.ws) as Vector<W>; |
||||||
|
|
||||||
|
// const encoded = transform(model, embed(model, source));
|
||||||
|
|
||||||
|
const embedded = this.embed(source); |
||||||
|
const logits = this.logitize(embedded); |
||||||
|
|
||||||
|
this.updateWOut(embedded, this.grad(logits, target), 0.001); |
||||||
|
|
||||||
|
const output = logits.map(this.sample); |
||||||
|
|
||||||
|
const a = 100 * target.filter((t, i) => output[i] === t).length / target.length; |
||||||
|
const l = this.loss(logits, target); |
||||||
|
|
||||||
|
const end: number = Date.now(); |
||||||
|
samples.push(end - begin); |
||||||
|
if ((epoch * runs + s) % Math.round(runs * epochs / Math.min(100, epochs)) === 0 || epoch === epochs - 1 && s === runs - 1 || end - log > 5000) { |
||||||
|
console.log(`Done ${samples.length} / ${epochs * runs}, accuracy ${a.toFixed(0)}%, loss ${l}, in ${end - begin}ms, ETA ${Math.round(samples.reduce((a, b) => a + b, 0) / samples.length * (epochs * runs - samples.length) / 1000)}s`); |
||||||
|
log = end; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public run(tids: TID[], samples: number = this.ws): TID[] { |
||||||
|
const inputs: Vector<W> = [ |
||||||
|
...Array(Math.max(0, this.ws - tids.length)).fill(0), |
||||||
|
...tids.slice(-this.ws)] as Vector<W>; |
||||||
|
const result: Vector<W, TID> = [] as unknown as Vector<W, TID>; |
||||||
|
while (result.length < samples) { |
||||||
|
const next: TID = this.sample(this.forward(inputs)[this.ws - 1]); |
||||||
|
result.push(next); |
||||||
|
inputs.shift(); |
||||||
|
inputs.push(next); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
public benchmark(data: TID[]): void { |
||||||
|
let log = Date.now(); |
||||||
|
const samples: number[] = []; |
||||||
|
console.log('Testing model.'); |
||||||
|
|
||||||
|
let accuracy: number = 0; |
||||||
|
let loss: number = 0; |
||||||
|
|
||||||
|
const runs = data.length - this.ws - 1; |
||||||
|
for (let s = 0; s < runs; s++) { |
||||||
|
const begin: number = Date.now(); |
||||||
|
|
||||||
|
const source = data.slice(s, s + this.ws) as Vector<W>; |
||||||
|
const target = data.slice(s + 1, s + 1 + this.ws) as Vector<W>; |
||||||
|
|
||||||
|
const embedded = this.embed(source); |
||||||
|
const logits = this.logitize(embedded); |
||||||
|
const output = logits.map(this.sample); |
||||||
|
|
||||||
|
const a = 100 * target.filter((t, i) => output[i] === t).length / target.length; |
||||||
|
const l = this.loss(logits, target); |
||||||
|
|
||||||
|
accuracy += a / runs; |
||||||
|
loss += l / runs; |
||||||
|
|
||||||
|
const end: number = Date.now(); |
||||||
|
samples.push(end - begin); |
||||||
|
if (end - log > 5000 || s === runs - 1) { |
||||||
|
console.log(`Done ${samples.length} / ${runs}, accuracy ${a.toFixed(0)}% / ${accuracy.toFixed()}%, loss ${l.toFixed(4)} / ${loss.toFixed(4)}, in ${end - begin}ms, ETA ${Math.round(samples.reduce((a, b) => a + b, 0) / samples.length * (runs - samples.length) / 1000)}s`); |
||||||
|
log = end; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public toJSON(): ModelData<V, W, D, H, HD, F, FD> { |
||||||
|
return { |
||||||
|
vs: this.vs, |
||||||
|
ws: this.ws, |
||||||
|
ds: this.ds, |
||||||
|
hs: this.hs, |
||||||
|
fs: this.fs, |
||||||
|
trained: true, |
||||||
|
positions: this.positions, |
||||||
|
embeddings: this.embeddings, |
||||||
|
weights: this.weights, |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,662 @@ |
|||||||
|
import fs from 'node:fs'; |
||||||
|
import { Writable } from 'node:stream'; |
||||||
|
import { Matrix } from './data/gpu/matrix'; |
||||||
|
import { Scalar } from './data/gpu/scalar'; |
||||||
|
import { Vector } from './data/gpu/vector'; |
||||||
|
import { Matrix as CPUMatrix, Vector as CPUVector, drm, mat, randseq, slope, stddev, vec } from './data/math'; |
||||||
|
import { TID, Tokenizer } from './tokenizer'; |
||||||
|
|
||||||
|
const SHADER_HOTONES = fs.readFileSync('shader/hotones.wgsl').toString(); |
||||||
|
const SHADER_EMBED = fs.readFileSync('shader/embed.wgsl').toString(); |
||||||
|
const SHADER_ATTW = fs.readFileSync('shader/attw.wgsl').toString(); |
||||||
|
const SHADER_SOFTMAX_EXP = fs.readFileSync('shader/softmaxexp.wgsl').toString(); |
||||||
|
const SHADER_SOFTMAX_NORM = fs.readFileSync('shader/softmaxnorm.wgsl').toString(); |
||||||
|
const SHADER_DLDO = fs.readFileSync('shader/dldo.wgsl').toString(); |
||||||
|
const SHADER_OTDLDO = fs.readFileSync('shader/otdldo.wgsl').toString(); |
||||||
|
const SHADER_TOTDLDOWA = fs.readFileSync('shader/totdldowa.wgsl').toString(); |
||||||
|
const SHADER_DLDS = fs.readFileSync('shader/dlds.wgsl').toString(); |
||||||
|
const SHADER_GRAD_TOUT = fs.readFileSync('shader/gradtout.wgsl').toString(); |
||||||
|
const SHADER_GRAD_TATTW = fs.readFileSync('shader/gradtattw.wgsl').toString(); |
||||||
|
const SHADER_GRAD_INP = fs.readFileSync('shader/gradinp.wgsl').toString(); |
||||||
|
const SHADER_ARGMAX = fs.readFileSync('shader/argmax.wgsl').toString(); |
||||||
|
const SHADER_SAMPLES = fs.readFileSync('shader/samples.wgsl').toString(); |
||||||
|
|
||||||
|
export interface ModelInitData< |
||||||
|
V extends number, |
||||||
|
W extends number, |
||||||
|
D extends number, |
||||||
|
> { |
||||||
|
readonly tokenizer: string; |
||||||
|
readonly vs: V; |
||||||
|
readonly ws: W; |
||||||
|
readonly ds: D; |
||||||
|
} |
||||||
|
|
||||||
|
export interface ModelData< |
||||||
|
V extends number, |
||||||
|
W extends number, |
||||||
|
D extends number, |
||||||
|
> extends ModelInitData<V, W, D> { |
||||||
|
stage: 'early' | 'mid' | 'end'; |
||||||
|
tokenizer: string; |
||||||
|
samples: number; |
||||||
|
weights: { |
||||||
|
inp: CPUMatrix<V, D>; |
||||||
|
pos: CPUMatrix<W, D>; |
||||||
|
tqry: CPUMatrix<D, D>; |
||||||
|
tkey: CPUMatrix<D, D>; |
||||||
|
tval: CPUMatrix<D, D>; |
||||||
|
out: CPUMatrix<V, D>; |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
interface TrainingPoint { |
||||||
|
index: number; |
||||||
|
timestamp: number; |
||||||
|
epoch: number; |
||||||
|
batch: number; |
||||||
|
lr: number; |
||||||
|
loss: number; |
||||||
|
ginp: number; |
||||||
|
gqry: number; |
||||||
|
gkey: number; |
||||||
|
gval: number; |
||||||
|
gout: number; |
||||||
|
accuracy: number; |
||||||
|
queueing: number; |
||||||
|
computing: number; |
||||||
|
inferring: number; |
||||||
|
} |
||||||
|
|
||||||
|
interface LogitsPoint { |
||||||
|
sample: number; |
||||||
|
token: number; |
||||||
|
itid: string; |
||||||
|
ttid: string; |
||||||
|
t0: string; |
||||||
|
o0: number; |
||||||
|
p0: number; |
||||||
|
t1: string; |
||||||
|
o1: number; |
||||||
|
p1: number; |
||||||
|
t2: string; |
||||||
|
o2: number; |
||||||
|
p2: number; |
||||||
|
tNm2: string; |
||||||
|
oNm2: number; |
||||||
|
pNm2: number; |
||||||
|
tNm1: string; |
||||||
|
oNm1: number; |
||||||
|
pNm1: number; |
||||||
|
tN: string; |
||||||
|
oN: number; |
||||||
|
pN: number; |
||||||
|
sum: number; |
||||||
|
stddev: number; |
||||||
|
entropy: number; |
||||||
|
loss: number; |
||||||
|
} |
||||||
|
|
||||||
|
export class Model< |
||||||
|
V extends number = number, |
||||||
|
W extends number = number, |
||||||
|
D extends number = number, |
||||||
|
> { |
||||||
|
protected readonly device: GPUDevice; |
||||||
|
public readonly tokenizer: Tokenizer; |
||||||
|
public readonly vs: V; |
||||||
|
public readonly ws: W; |
||||||
|
public readonly ds: D; |
||||||
|
|
||||||
|
protected samples: number = 0; |
||||||
|
protected stage: 'early' | 'mid' | 'end' = 'early'; |
||||||
|
|
||||||
|
protected readonly weights: { |
||||||
|
readonly inp: Matrix<V, D, Float32Array>; |
||||||
|
readonly pos: Matrix<W, D, Float32Array>; |
||||||
|
readonly tqry: Matrix<D, D, Float32Array>; |
||||||
|
readonly tkey: Matrix<D, D, Float32Array>; |
||||||
|
readonly tval: Matrix<D, D, Float32Array>; |
||||||
|
readonly tout: Matrix<V, D, Float32Array>; |
||||||
|
}; |
||||||
|
|
||||||
|
protected readonly processing: { |
||||||
|
readonly ds: Scalar<Float32Array>; |
||||||
|
readonly lr: Scalar<Float32Array>; |
||||||
|
readonly itids: Vector<W, Int32Array, TID>; |
||||||
|
readonly ilogits: Matrix<W, V, Float32Array>; |
||||||
|
readonly e: Matrix<W, D, Float32Array>; |
||||||
|
readonly q: Matrix<W, D, Float32Array>; |
||||||
|
readonly k: Matrix<W, D, Float32Array>; |
||||||
|
readonly tk: Matrix<D, W, Float32Array>; |
||||||
|
readonly qtk: Matrix<W, W, Float32Array>; |
||||||
|
readonly v: Matrix<W, D, Float32Array>; |
||||||
|
readonly s: Matrix<W, W, Float32Array>; |
||||||
|
readonly se: Matrix<W, W, Float32Array>; |
||||||
|
readonly wa: Matrix<W, W, Float32Array>; |
||||||
|
readonly a: Matrix<W, D, Float32Array>; |
||||||
|
readonly o: Matrix<W, V, Float32Array>; |
||||||
|
readonly temp: Scalar<Float32Array>; |
||||||
|
readonly tempo: Matrix<W, V, Float32Array>; |
||||||
|
readonly on: Matrix<W, V, Float32Array>; |
||||||
|
readonly ologits: Matrix<W, V, Float32Array>; |
||||||
|
readonly dldo: Matrix<W, V, Float32Array>; |
||||||
|
readonly otdldo: Matrix<D, W, Float32Array>; |
||||||
|
readonly totdldowa: Matrix<W, D, Float32Array>; |
||||||
|
readonly dlds: Matrix<W, W, Float32Array>; |
||||||
|
readonly dldsk: Matrix<W, D, Float32Array>; |
||||||
|
readonly dldsq: Matrix<W, D, Float32Array>; |
||||||
|
readonly ginp: Matrix<V, D, Float32Array>; |
||||||
|
readonly gtqry: Matrix<D, D, Float32Array>; |
||||||
|
readonly gtkey: Matrix<D, D, Float32Array>; |
||||||
|
readonly gtval: Matrix<D, D, Float32Array>; |
||||||
|
readonly gtout: Matrix<V, D, Float32Array>; |
||||||
|
readonly rnd: Vector<W, Float32Array>; |
||||||
|
readonly otids: Vector<W, Int32Array, TID>; |
||||||
|
readonly ttids: Vector<W, Int32Array, TID>; |
||||||
|
}; |
||||||
|
|
||||||
|
protected softmax<H extends number, W extends number>( |
||||||
|
m: Matrix<H, W, Float32Array>, |
||||||
|
e: Matrix<H, W, Float32Array> = new Matrix(this.device, Float32Array, m.height, m.width), |
||||||
|
r: Matrix<H, W, Float32Array> = new Matrix(this.device, Float32Array, m.height, m.width), |
||||||
|
): Matrix<H, W, Float32Array> { |
||||||
|
e.calculate('softmaxexp', SHADER_SOFTMAX_EXP, [m], false, 4); |
||||||
|
r.calculate('softmaxnorm', SHADER_SOFTMAX_NORM, [e], false, 4); |
||||||
|
return r; |
||||||
|
} |
||||||
|
|
||||||
|
protected async ilogits(itids: Vector<W, Int32Array, TID> = this.processing.itids): Promise<Matrix<W, V, Float32Array>> { |
||||||
|
this.processing.ilogits.calculate('hotones', SHADER_HOTONES, [itids]); |
||||||
|
return this.processing.ilogits; |
||||||
|
} |
||||||
|
|
||||||
|
protected async e(ilogits: Matrix<W, V, Float32Array> = this.processing.ilogits): Promise<Matrix<W, D, Float32Array>> { |
||||||
|
this.processing.e.calculate('embed', SHADER_EMBED, [ilogits, this.weights.inp, this.weights.pos]); |
||||||
|
return this.processing.e; |
||||||
|
} |
||||||
|
|
||||||
|
protected q(e: Matrix<W, D, Float32Array> = this.processing.e): Matrix<W, D, Float32Array> { |
||||||
|
this.processing.q.calculate('attw', SHADER_ATTW, [e, this.weights.tqry]); |
||||||
|
return this.processing.q; |
||||||
|
} |
||||||
|
|
||||||
|
protected k(e: Matrix<W, D, Float32Array> = this.processing.e): Matrix<W, D, Float32Array> { |
||||||
|
this.processing.k.calculate('attw', SHADER_ATTW, [e, this.weights.tkey]); |
||||||
|
return this.processing.k; |
||||||
|
} |
||||||
|
|
||||||
|
protected v(e: Matrix<W, D, Float32Array> = this.processing.e): Matrix<W, D, Float32Array> { |
||||||
|
this.processing.v.calculate('attw', SHADER_ATTW, [e, this.weights.tval]); |
||||||
|
return this.processing.v; |
||||||
|
} |
||||||
|
|
||||||
|
protected s( |
||||||
|
q: Matrix<W, D, Float32Array> = this.processing.q, |
||||||
|
k: Matrix<W, D, Float32Array> = this.processing.k, |
||||||
|
): Matrix<W, W, Float32Array> { |
||||||
|
Matrix.transpose(k, this.processing.tk); |
||||||
|
Matrix.mul(q, this.processing.tk, this.processing.qtk); |
||||||
|
Matrix.scale(this.processing.ds, this.processing.qtk, this.processing.s); |
||||||
|
return this.processing.s; |
||||||
|
} |
||||||
|
|
||||||
|
protected async wa(s: Matrix<W, W, Float32Array> = this.processing.s): Promise<Matrix<W, W, Float32Array>> { |
||||||
|
this.softmax(s, this.processing.se, this.processing.wa); |
||||||
|
return this.processing.wa; |
||||||
|
} |
||||||
|
|
||||||
|
protected a( |
||||||
|
wa: Matrix<W, W, Float32Array> = this.processing.wa, |
||||||
|
v: Matrix<W, D, Float32Array> = this.processing.v, |
||||||
|
): Matrix<W, D, Float32Array> { |
||||||
|
Matrix.mul(wa, v, this.processing.a); |
||||||
|
return this.processing.a; |
||||||
|
} |
||||||
|
|
||||||
|
protected o(a: Matrix<W, D, Float32Array> = this.processing.a): Matrix<W, V, Float32Array> { |
||||||
|
this.processing.o.calculate('attw', SHADER_ATTW, [a, this.weights.tout]); |
||||||
|
return this.processing.o; |
||||||
|
} |
||||||
|
|
||||||
|
protected async ologits(o: Matrix<W, V, Float32Array> = this.processing.o, temp: number = 1): Promise<Matrix<W, V, Float32Array>> { |
||||||
|
await this.processing.temp.set(temp); |
||||||
|
Matrix.scale(this.processing.temp, o, this.processing.tempo); |
||||||
|
this.softmax(this.processing.tempo, this.processing.on, this.processing.ologits); |
||||||
|
return this.processing.ologits; |
||||||
|
} |
||||||
|
|
||||||
|
protected async rnd(): Promise<Vector<W, Float32Array>> { |
||||||
|
await this.processing.rnd.set(vec(this.ws, () => Math.random())); |
||||||
|
return this.processing.rnd; |
||||||
|
} |
||||||
|
|
||||||
|
protected otids(): Vector<W, Int32Array, TID>; |
||||||
|
protected otids(ologits: Matrix<W, V, Float32Array>): Vector<W, Int32Array, TID>; |
||||||
|
protected otids(rnd: Vector<W, Float32Array>, ologits: Matrix<W, V, Float32Array>): Vector<W, Int32Array, TID>; |
||||||
|
protected otids(...args: unknown[]): Vector<W, Int32Array, TID> { |
||||||
|
if (args.length === 1) { |
||||||
|
const [ologits = this.processing.ologits] = args as [Matrix<W, V, Float32Array>]; |
||||||
|
this.processing.otids.calculate('argmax', SHADER_ARGMAX, [ologits]); |
||||||
|
return this.processing.otids; |
||||||
|
} else { |
||||||
|
const [rnd = this.processing.rnd, ologits = this.processing.ologits] = args as [Vector<W, Float32Array> | undefined, Matrix<W, V, Float32Array> | undefined]; |
||||||
|
this.processing.otids.calculate('samples', SHADER_SAMPLES, [ologits, rnd]); |
||||||
|
return this.processing.otids; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected loss<N extends number>(probs: CPUVector<N, CPUVector<V>>, target: CPUVector<N, TID>): number { |
||||||
|
return probs.reduce((l: number, ps: CPUVector<V>, i: number) => l - Math.log(ps[target[i]] + 1e-10), 0) / probs.length; |
||||||
|
} |
||||||
|
|
||||||
|
protected async upd( |
||||||
|
ilogits: Matrix<W, V, Float32Array> = this.processing.ilogits, |
||||||
|
e: Matrix<W, D, Float32Array> = this.processing.e, |
||||||
|
q: Matrix<W, D, Float32Array> = this.processing.q, |
||||||
|
k: Matrix<W, D, Float32Array> = this.processing.k, |
||||||
|
v: Matrix<W, D, Float32Array> = this.processing.v, |
||||||
|
wa: Matrix<W, W, Float32Array> = this.processing.wa, |
||||||
|
a: Matrix<W, D, Float32Array> = this.processing.a, |
||||||
|
ologits: Matrix<W, V, Float32Array> = this.processing.ologits, |
||||||
|
ttids: Vector<W, Int32Array, TID> = this.processing.ttids, |
||||||
|
lr: number = 0.001, |
||||||
|
): Promise<void> { |
||||||
|
await this.processing.lr.set(-lr); |
||||||
|
|
||||||
|
this.processing.dldo.calculate('dldo', SHADER_DLDO, [ |
||||||
|
ologits, |
||||||
|
ttids, |
||||||
|
this.processing.lr, |
||||||
|
]); |
||||||
|
|
||||||
|
this.processing.otdldo.calculate('otdldo', SHADER_OTDLDO, [this.processing.dldo, this.weights.tout]); |
||||||
|
|
||||||
|
this.processing.dlds.calculate('dlds', SHADER_DLDS, [ |
||||||
|
this.processing.ds, |
||||||
|
wa, |
||||||
|
v, |
||||||
|
this.processing.otdldo, |
||||||
|
]); |
||||||
|
|
||||||
|
Matrix.mul(this.processing.dlds, k, this.processing.dldsk); |
||||||
|
Matrix.mul(Matrix.transpose(this.processing.dlds), q, this.processing.dldsq); |
||||||
|
|
||||||
|
this.processing.ginp.calculate('gradinp', SHADER_GRAD_INP, [ |
||||||
|
ilogits, |
||||||
|
this.processing.dldsq, |
||||||
|
this.processing.dldsk, |
||||||
|
this.processing.totdldowa, |
||||||
|
this.weights.tqry, |
||||||
|
this.weights.tkey, |
||||||
|
this.weights.tval, |
||||||
|
]); |
||||||
|
Matrix.sum(this.processing.ginp, this.weights.inp, this.weights.inp); |
||||||
|
|
||||||
|
this.processing.gtout.calculate('gradtout', SHADER_GRAD_TOUT, [a, this.processing.dldo]); |
||||||
|
Matrix.sum(this.processing.gtout, this.weights.tout, this.weights.tout); |
||||||
|
this.processing.gtqry.calculate('gradtqry', SHADER_GRAD_TATTW, [e, this.processing.dldsk]); |
||||||
|
Matrix.sum(this.processing.gtqry, this.weights.tqry, this.weights.tqry); |
||||||
|
this.processing.gtkey.calculate('gradtkey', SHADER_GRAD_TATTW, [e, this.processing.dldsq]); |
||||||
|
Matrix.sum(this.processing.gtkey, this.weights.tkey, this.weights.tkey); |
||||||
|
|
||||||
|
this.processing.totdldowa.calculate('totdldowa', SHADER_TOTDLDOWA, [wa, this.processing.otdldo]); |
||||||
|
this.processing.gtval.calculate('gradtval', SHADER_GRAD_TATTW, [e, this.processing.totdldowa]); |
||||||
|
Matrix.sum(this.processing.gtval, this.weights.tval, this.weights.tval); |
||||||
|
} |
||||||
|
|
||||||
|
public constructor( |
||||||
|
device: GPUDevice, |
||||||
|
tokenizer: Tokenizer, |
||||||
|
data: ModelInitData<V, W, D> | ModelData<V, W, D>, |
||||||
|
) { |
||||||
|
this.device = device; |
||||||
|
this.tokenizer = tokenizer; |
||||||
|
this.vs = data.vs; |
||||||
|
this.ws = data.ws; |
||||||
|
this.ds = data.ds; |
||||||
|
if (!('stage' in data)) { |
||||||
|
this.weights = { |
||||||
|
inp: new Matrix(this.device, Float32Array, mat(this.vs, this.ds, () => (Math.random() - 0.5) * Math.sqrt(1 / this.ds))), |
||||||
|
pos: new Matrix(this.device, Float32Array, mat(this.ws, this.ds, (i, d) => { |
||||||
|
const angle = i / Math.pow(10000, 2 * Math.floor(d / 2) / this.ds); |
||||||
|
return (d % 2 === 0 ? Math.sin(angle) : Math.cos(angle)) * Math.sqrt(1 / this.ds); |
||||||
|
})), |
||||||
|
tqry: new Matrix(this.device, Float32Array, mat(this.ds, this.ds, () => (Math.random() - 0.5) * Math.sqrt(1 / this.ds))), |
||||||
|
tkey: new Matrix(this.device, Float32Array, mat(this.ds, this.ds, () => (Math.random() - 0.5) * Math.sqrt(1 / this.ds))), |
||||||
|
tval: new Matrix(this.device, Float32Array, mat(this.ds, this.ds, () => (Math.random() - 0.5) * Math.sqrt(1 / this.ds))), |
||||||
|
tout: new Matrix(this.device, Float32Array, mat(this.vs, this.ds, () => (Math.random() - 0.5) * Math.sqrt(1 / this.ds))), |
||||||
|
}; |
||||||
|
} else { |
||||||
|
this.weights = { |
||||||
|
inp: new Matrix(this.device, Float32Array, data.weights.inp), |
||||||
|
pos: new Matrix(this.device, Float32Array, data.weights.pos), |
||||||
|
tqry: new Matrix(this.device, Float32Array, data.weights.tqry), |
||||||
|
tkey: new Matrix(this.device, Float32Array, data.weights.tkey), |
||||||
|
tval: new Matrix(this.device, Float32Array, data.weights.tval), |
||||||
|
tout: new Matrix(this.device, Float32Array, data.weights.out), |
||||||
|
}; |
||||||
|
this.stage = data.stage; |
||||||
|
this.samples = data.samples; |
||||||
|
if (data.tokenizer !== `${tokenizer.tokens.length}${tokenizer.tokens[0]}`) { |
||||||
|
throw new Error(`The model was trained against a different tokenizer ("${data.tokenizer}").`); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
this.processing = { |
||||||
|
ds: new Scalar<Float32Array>(this.device, Float32Array, 1 / Math.sqrt(this.ds)), |
||||||
|
lr: new Scalar<Float32Array>(this.device, Float32Array, 0), |
||||||
|
itids: new Vector<W, Int32Array, TID>(this.device, Int32Array, this.ws), |
||||||
|
ilogits: new Matrix<W, V, Float32Array>(this.device, Float32Array, this.ws, this.vs), |
||||||
|
e: new Matrix<W, D, Float32Array>(this.device, Float32Array, this.ws, this.ds), |
||||||
|
q: new Matrix<W, D, Float32Array>(this.device, Float32Array, this.ws, this.ds), |
||||||
|
k: new Matrix<W, D, Float32Array>(this.device, Float32Array, this.ws, this.ds), |
||||||
|
tk: new Matrix<D, W, Float32Array>(this.device, Float32Array, this.ds, this.ws), |
||||||
|
qtk: new Matrix<W, W, Float32Array>(this.device, Float32Array, this.ws, this.ws), |
||||||
|
v: new Matrix<W, D, Float32Array>(this.device, Float32Array, this.ws, this.ds), |
||||||
|
s: new Matrix<W, W, Float32Array>(this.device, Float32Array, this.ws, this.ws), |
||||||
|
se: new Matrix<W, W, Float32Array>(this.device, Float32Array, this.ws, this.ws), |
||||||
|
wa: new Matrix<W, W, Float32Array>(this.device, Float32Array, this.ws, this.ws), |
||||||
|
a: new Matrix<W, D, Float32Array>(this.device, Float32Array, this.ws, this.ds), |
||||||
|
o: new Matrix<W, V, Float32Array>(this.device, Float32Array, this.ws, this.vs), |
||||||
|
temp: new Scalar<Float32Array>(this.device, Float32Array, 1), |
||||||
|
tempo: new Matrix<W, V, Float32Array>(this.device, Float32Array, this.ws, this.vs), |
||||||
|
on: new Matrix<W, V, Float32Array>(this.device, Float32Array, this.ws, this.vs), |
||||||
|
ologits: new Matrix<W, V, Float32Array>(this.device, Float32Array, this.ws, this.vs), |
||||||
|
dldo: new Matrix<W, V, Float32Array>(this.device, Float32Array, this.ws, this.vs), |
||||||
|
otdldo: new Matrix<D, W, Float32Array>(this.device, Float32Array, this.ds, this.ws), |
||||||
|
totdldowa: new Matrix<W, D, Float32Array>(this.device, Float32Array, this.ws, this.ds), |
||||||
|
dlds: new Matrix<W, W, Float32Array>(this.device, Float32Array, this.ws, this.ws), |
||||||
|
dldsk: new Matrix<W, D, Float32Array>(this.device, Float32Array, this.ws, this.ds), |
||||||
|
dldsq: new Matrix<W, D, Float32Array>(this.device, Float32Array, this.ws, this.ds), |
||||||
|
ginp: new Matrix(this.device, Float32Array, this.vs, this.ds), |
||||||
|
gtqry: new Matrix(this.device, Float32Array, this.ds, this.ds), |
||||||
|
gtkey: new Matrix(this.device, Float32Array, this.ds, this.ds), |
||||||
|
gtval: new Matrix(this.device, Float32Array, this.ds, this.ds), |
||||||
|
gtout: new Matrix(this.device, Float32Array, this.vs, this.ds), |
||||||
|
rnd: new Vector<W, Float32Array>(this.device, Float32Array, this.ws), |
||||||
|
otids: new Vector<W, Int32Array, TID>(this.device, Int32Array, this.ws), |
||||||
|
ttids: new Vector<W, Int32Array, TID>(this.device, Int32Array, this.ws), |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
public async debug( |
||||||
|
sample: number, |
||||||
|
itids: Vector<W, Int32Array, TID> = this.processing.itids, |
||||||
|
ttids: Vector<W, Int32Array, TID> = this.processing.ttids, |
||||||
|
o: Matrix<W, V, Float32Array> = this.processing.o, |
||||||
|
ologits: Matrix<W, V, Float32Array> = this.processing.ologits, |
||||||
|
file?: Writable, |
||||||
|
llo: boolean = false, |
||||||
|
): Promise<void> { |
||||||
|
if (!file) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
for (let token = llo ? itids.dims - 1 : 0; token < itids.dims; token++) { |
||||||
|
const os = (await o.get())[token]; |
||||||
|
const ls = (await ologits.get())[token] |
||||||
|
.map((p, tid) => [p, tid]) |
||||||
|
.sort(([l], [r]) => r - l); |
||||||
|
|
||||||
|
const point: LogitsPoint = { |
||||||
|
sample, |
||||||
|
token, |
||||||
|
itid: this.tokenizer.detokenize([await itids.get(token)])[0], |
||||||
|
ttid: this.tokenizer.detokenize([await ttids.get(token)])[0], |
||||||
|
t0: this.tokenizer.detokenize([ls[0][1]])[0], |
||||||
|
o0: os[ls[0][1]], |
||||||
|
p0: ls[0][0], |
||||||
|
t1: this.tokenizer.detokenize([ls[1][1]])[0], |
||||||
|
o1: os[ls[1][1]], |
||||||
|
p1: ls[1][0], |
||||||
|
t2: this.tokenizer.detokenize([ls[2][1]])[0], |
||||||
|
o2: os[ls[2][1]], |
||||||
|
p2: ls[2][0], |
||||||
|
tNm2: this.tokenizer.detokenize([ls[ls.length - 3][1]])[0], |
||||||
|
oNm2: os[ls[ls.length - 3][1]], |
||||||
|
pNm2: ls[ls.length - 3][0], |
||||||
|
tNm1: this.tokenizer.detokenize([ls[ls.length - 2][1]])[0], |
||||||
|
oNm1: os[ls[ls.length - 3][1]], |
||||||
|
pNm1: ls[ls.length - 2][0], |
||||||
|
tN: this.tokenizer.detokenize([ls[ls.length - 1][1]])[0], |
||||||
|
oN: os[ls[ls.length - 1][1]], |
||||||
|
pN: ls[ls.length - 1][0], |
||||||
|
sum: (await ologits.get())[token].reduce((a, b) => a + b, 0), |
||||||
|
stddev: stddev((await o.get())[token]), |
||||||
|
entropy: -(await ologits.get())[token].reduce((sum, p) => sum + (p > 0 ? p * Math.log(p) : 0), 0), |
||||||
|
loss: -Math.log((await ologits.get())[token][await ttids.get(token)] + 1e-10), |
||||||
|
}; |
||||||
|
file.write(`${JSON.stringify(Object.values(point)).slice(1, -1)}\n`); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public async train( |
||||||
|
duration: number | 'auto', |
||||||
|
data: string, |
||||||
|
{ logs = process.stderr, stats, logits, samples, snapshot, statsInterval = 5000, snapshotInterval = 5000 }: { |
||||||
|
logs?: Writable; |
||||||
|
stats?: Writable; |
||||||
|
logits?: Writable; |
||||||
|
samples?: Writable; |
||||||
|
snapshot?: (json: object) => void; |
||||||
|
statsInterval?: number; |
||||||
|
snapshotInterval?: number; |
||||||
|
} = {}, |
||||||
|
): Promise<void> { |
||||||
|
logs.write('Training model...\n'); |
||||||
|
|
||||||
|
let logged = Date.now(); |
||||||
|
let snapshotted = Date.now(); |
||||||
|
|
||||||
|
const aw: number = 10; |
||||||
|
|
||||||
|
const times = []; |
||||||
|
const losses = []; |
||||||
|
|
||||||
|
let epochs = duration === 'auto' ? Infinity : +duration; |
||||||
|
const tids = this.tokenizer.tokenize(data); |
||||||
|
const batches = tids.length - this.ws - 1; |
||||||
|
|
||||||
|
let lr = 0.003; |
||||||
|
|
||||||
|
for (let epoch = 0; epoch < epochs; epoch++) { |
||||||
|
const idxs = randseq(tids.length); |
||||||
|
for (let batch = 0; batch < batches; batch++) { |
||||||
|
const begin: number = Date.now(); |
||||||
|
|
||||||
|
const si = idxs[batch]; |
||||||
|
await this.processing.itids.set(tids.slice(si, si + this.ws) as CPUVector<W>); |
||||||
|
await this.processing.ttids.set(tids.slice(si + 1, si + 1 + this.ws) as CPUVector<W>); |
||||||
|
|
||||||
|
const ilogits = await this.ilogits(this.processing.itids); |
||||||
|
const e = await this.e(ilogits); |
||||||
|
const q = this.q(e); |
||||||
|
const k = this.k(e); |
||||||
|
const v = this.v(e); |
||||||
|
const s = this.s(q, k); |
||||||
|
const wa = await this.wa(s); |
||||||
|
const a = this.a(wa, v); |
||||||
|
const o = this.o(a); |
||||||
|
const ologits = await this.ologits(o); |
||||||
|
await this.upd(ilogits, e, q, k, v, wa, a, ologits, this.processing.ttids, lr); |
||||||
|
|
||||||
|
const queued: number = Date.now(); |
||||||
|
|
||||||
|
await this.device.queue.onSubmittedWorkDone(); |
||||||
|
|
||||||
|
const end: number = Date.now(); |
||||||
|
|
||||||
|
this.samples += 1; |
||||||
|
times.push(end - begin); |
||||||
|
|
||||||
|
if ( |
||||||
|
epoch === 0 && batch === 0 |
||||||
|
|| batch === batches - 1 |
||||||
|
|| end - logged > statsInterval |
||||||
|
) { |
||||||
|
const point: TrainingPoint = { |
||||||
|
index: this.samples, |
||||||
|
timestamp: Date.now(), |
||||||
|
epoch, |
||||||
|
batch, |
||||||
|
lr, |
||||||
|
loss: 0, |
||||||
|
accuracy: 0, |
||||||
|
ginp: (await this.processing.ginp.get()).reduce((s, r) => r.reduce((s, v) => s + v * v, s), 0) / lr / lr, |
||||||
|
gqry: (await this.processing.gtqry.get()).reduce((s, r) => r.reduce((s, v) => s + v * v, s), 0) / lr / lr, |
||||||
|
gkey: (await this.processing.gtkey.get()).reduce((s, r) => r.reduce((s, v) => s + v * v, s), 0) / lr / lr, |
||||||
|
gval: (await this.processing.gtval.get()).reduce((s, r) => r.reduce((s, v) => s + v * v, s), 0) / lr / lr, |
||||||
|
gout: (await this.processing.gtout.get()).reduce((s, r) => r.reduce((s, v) => s + v * v, s), 0) / lr / lr, |
||||||
|
queueing: 0, |
||||||
|
computing: 0, |
||||||
|
inferring: 0, |
||||||
|
}; |
||||||
|
point.queueing = Date.now() - begin; |
||||||
|
point.computing = Date.now() - queued; |
||||||
|
|
||||||
|
const out = await this.otids(await this.rnd(), ologits).get(); |
||||||
|
const tgt = await this.processing.ttids.get(); |
||||||
|
|
||||||
|
const inferred: number = Date.now(); |
||||||
|
|
||||||
|
point.inferring = inferred - end; |
||||||
|
|
||||||
|
point.accuracy = tgt.filter((t, i) => out[i] === t).length / tgt.length; |
||||||
|
|
||||||
|
point.loss = this.loss(await ologits.get(), tgt); |
||||||
|
|
||||||
|
losses.push(point.loss); |
||||||
|
|
||||||
|
if (this.stage === 'early') { |
||||||
|
lr = Math.max(0.001, Math.min(0.003, point.loss * 0.001)); |
||||||
|
|
||||||
|
if (losses.length > aw && (slope(losses.slice(-aw)) > -0.005 || stddev(losses.slice(-aw)) < 0.2)) { |
||||||
|
logs?.write('Switching to mid stage.\n'); |
||||||
|
this.stage = 'mid'; |
||||||
|
} |
||||||
|
} else if (this.stage === 'mid') { |
||||||
|
lr = Math.max(0.0003, lr * 0.98); |
||||||
|
|
||||||
|
if (losses.length > aw && Math.abs(slope(losses.slice(-aw))) < 0.002 && stddev(losses.slice(-aw)) < 0.05) { |
||||||
|
logs?.write('Switching to end stage.\n'); |
||||||
|
this.stage = 'end'; |
||||||
|
} |
||||||
|
} else { |
||||||
|
lr = Math.max(1e-4, lr * 0.98); |
||||||
|
} |
||||||
|
|
||||||
|
logs?.write(`Done ${epoch * batches + batch + 1} / ${epochs * batches}, ETA ${Math.round(drm(times) * (epochs * batches - times.length) / 1000)}s\n`); |
||||||
|
stats?.write(`${Object.values(point).join(',')}\n`); |
||||||
|
await this.debug(this.samples, this.processing.itids, this.processing.ttids, o, ologits, logits); |
||||||
|
|
||||||
|
logged = end; |
||||||
|
} |
||||||
|
|
||||||
|
if (batch === 0 && end - snapshotted > snapshotInterval) { |
||||||
|
if (duration === 'auto' && this.stage === 'end' && losses.length > aw && ( |
||||||
|
Math.abs(slope(losses.slice(-aw))) < 0.001 && stddev(losses.slice(-aw)) < 0.001 |
||||||
|
|| slope(losses.slice(-aw)) > 0.001 |
||||||
|
|| stddev(losses.slice(-aw)) > 1)) { |
||||||
|
logs?.write(`Ending training with slope=${slope(losses.slice(-aw))} stddev=${stddev(losses.slice(-aw))}.\n`); |
||||||
|
epochs = 0; |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
snapshot?.(await this.toJSON()); |
||||||
|
|
||||||
|
samples?.write(`Epoch ${epoch + 1}:\n`); |
||||||
|
samples?.write('Sample (temp=0.3):\n'); |
||||||
|
samples?.write(this.tokenizer.detokenize(await this.run(this.tokenizer.tokenize('Hello?'), { temp: 0.3 })).join('')); |
||||||
|
samples?.write('\nEnd of sample.\n'); |
||||||
|
samples?.write('Sample (temp=0.7):\n'); |
||||||
|
samples?.write(this.tokenizer.detokenize(await this.run(this.tokenizer.tokenize('Hello?'), { temp: 0.7 })).join('')); |
||||||
|
samples?.write('\nEnd of sample.\n'); |
||||||
|
samples?.write('Sample (temp=1):\n'); |
||||||
|
samples?.write(this.tokenizer.detokenize(await this.run(this.tokenizer.tokenize('Hello?'), { temp: 1 })).join('')); |
||||||
|
samples?.write('\nEnd of sample.\n'); |
||||||
|
samples?.write('Sample (temp=1.3):\n'); |
||||||
|
samples?.write(this.tokenizer.detokenize(await this.run(this.tokenizer.tokenize('Hello?'), { temp: 1.3 })).join('')); |
||||||
|
samples?.write('\nEnd of sample.\n\n'); |
||||||
|
samples?.write('Sample (argmax):\n'); |
||||||
|
samples?.write(this.tokenizer.detokenize(await this.run(this.tokenizer.tokenize('Hello?'), { temp: 'max' })).join('')); |
||||||
|
samples?.write('\nEnd of sample.\n\n'); |
||||||
|
|
||||||
|
snapshotted = end; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
logs?.write('Training completed.\n'); |
||||||
|
} |
||||||
|
|
||||||
|
public async transform( |
||||||
|
tids: TID[], |
||||||
|
{ temp = 1, logits, llo = false }: { |
||||||
|
temp?: number | 'max'; |
||||||
|
logits?: Writable; |
||||||
|
llo?: boolean; |
||||||
|
} = {}, |
||||||
|
): Promise<[TID[], TID[]]> { |
||||||
|
const itids = [ |
||||||
|
...Array(Math.max(0, this.ws - tids.length)).fill(0), |
||||||
|
...tids.slice(-this.ws), |
||||||
|
]; |
||||||
|
|
||||||
|
await this.processing.itids.set(itids); |
||||||
|
await this.processing.ttids.set([...itids.slice(1), 0]); |
||||||
|
const ilogits = await this.ilogits(this.processing.itids); |
||||||
|
const e = await this.e(ilogits); |
||||||
|
const q = this.q(e); |
||||||
|
const k = this.k(e); |
||||||
|
const v = this.v(e); |
||||||
|
const s = this.s(q, k); |
||||||
|
const wa = await this.wa(s); |
||||||
|
const a = this.a(wa, v); |
||||||
|
const o = this.o(a); |
||||||
|
const ologits = await this.ologits(o, temp === 'max' ? 1 : temp); |
||||||
|
const out = temp === 'max' ? this.otids(ologits) : this.otids(await this.rnd(), ologits); |
||||||
|
const otids = await out.get(); |
||||||
|
|
||||||
|
await this.debug(0, this.processing.itids, this.processing.ttids, o, ologits, logits, llo); |
||||||
|
|
||||||
|
return [itids, otids]; |
||||||
|
} |
||||||
|
|
||||||
|
public async run( |
||||||
|
tids: TID[], |
||||||
|
{ temp = 1, samples = this.ws, logits }: { |
||||||
|
temp?: number | 'max'; |
||||||
|
samples?: number; |
||||||
|
logits?: Writable; |
||||||
|
} = {}, |
||||||
|
): Promise<TID[]> { |
||||||
|
const result: TID[] = []; |
||||||
|
while (result.length < samples) { |
||||||
|
const [_, out] = await this.transform(tids, { temp, logits, llo: true }); |
||||||
|
const next: TID = out[this.ws - 1]; |
||||||
|
result.push(next); |
||||||
|
tids.shift(); |
||||||
|
tids.push(next); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
public async toJSON(): Promise<ModelData<V, W, D>> { |
||||||
|
return { |
||||||
|
tokenizer: `${this.tokenizer.tokens.length}${this.tokenizer.tokens[0]}`, |
||||||
|
vs: this.vs, |
||||||
|
ws: this.ws, |
||||||
|
ds: this.ds, |
||||||
|
stage: this.stage, |
||||||
|
samples: this.samples, |
||||||
|
weights: { |
||||||
|
inp: await this.weights.inp.get(), |
||||||
|
pos: await this.weights.pos.get(), |
||||||
|
tqry: await this.weights.tqry.get(), |
||||||
|
tkey: await this.weights.tkey.get(), |
||||||
|
tval: await this.weights.tval.get(), |
||||||
|
out: await this.weights.tout.get(), |
||||||
|
}, |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
@ -1,9 +0,0 @@ |
|||||||
import { _, _l, g, n } from './core'; |
|
||||||
import { iif } from './bool'; |
|
||||||
import { fromNumber as _n, div, lt, mod, sum } from './num'; |
|
||||||
import { char } from './char'; |
|
||||||
import { Empty, append, cons } from './string'; |
|
||||||
|
|
||||||
n('num.show'); |
|
||||||
|
|
||||||
export const show = g('show', _l(n => iif._(lt._(n)._(_n(10)))._(cons._(char._(sum._(n)._(_n(48))))._(Empty))._(append._(show._(div._(n)._(_n(10))))._(show._(mod._(n)._(_n(10))))))); |
|
||||||
@ -1,45 +0,0 @@ |
|||||||
import { $, Term, _, _l, cnst, g, id, n, pipe } from './core'; |
|
||||||
import { EQ, GT, LT } from './ord'; |
|
||||||
import { False, True, iif } from './bool'; |
|
||||||
|
|
||||||
n('num'); |
|
||||||
|
|
||||||
export const Zero = g('Zero', _l(z => _l(_ => z))); |
|
||||||
|
|
||||||
export const Succ = g('Succ', _l(p => _l(_ => _l(n => n._(p))))); |
|
||||||
|
|
||||||
export const ifz = g('ifz', _l(n => _l(t => _l(f => n._(t)._(cnst._(f)))))); |
|
||||||
|
|
||||||
export const succ = g('succ', Succ); |
|
||||||
|
|
||||||
export const pred = g('pred', _l(n => n._(Zero)._(id))); |
|
||||||
|
|
||||||
export const cmp = g('cmp', _l(l => _l(r => l._(r._(EQ)._(cnst._(LT)))._(pipe._(r._(GT))._(cmp))))); |
|
||||||
|
|
||||||
export const lt = g('lt', _l(l => _l(r => r._(False)._(l._(cnst._(True))._(lt))))); |
|
||||||
|
|
||||||
export const le = g('le', _l(l => _l(r => l._(True)._(r._(cnst._(False))._(ge))))); |
|
||||||
|
|
||||||
export const eq = g('eq', _l(l => _l(r => l._(r._(True)._(cnst._(False)))._(r._(cnst._(False))._(eq))))); |
|
||||||
|
|
||||||
export const ge = g('ge', _l(l => _l(r => r._(True)._(l._(cnst._(False))._(ge))))); |
|
||||||
|
|
||||||
export const gt = g('gt', _l(l => _l(r => l._(False)._(r._(cnst._(True))._(lt))))); |
|
||||||
|
|
||||||
export const even = g('even', _l(n => n._(True)._(odd))); |
|
||||||
|
|
||||||
export const odd = g('odd', _l(n => n._(False)._(even))); |
|
||||||
|
|
||||||
export const sum = g('sum', _l(l => _l(r => r._(l)._(sum._(Succ._(l)))))); |
|
||||||
|
|
||||||
export const sub = g('sub', _l(l => _l(r => r._(l)._(l._(cnst._(Zero))._(sub))))); |
|
||||||
|
|
||||||
export const mul = g('mul', _l(l => _l(r => l._(Zero)._(_l(pl => sum._(r)._(mul._(pl)._(r))))))); |
|
||||||
|
|
||||||
export const div = g('div', _l(l => _l(r => iif._(lt._(l)._(r))._(Zero)._(succ._(div._(sub._(l)._(r))._(r)))))); |
|
||||||
|
|
||||||
export const mod = g('mod', _l(l => _l(r => iif._(lt._(l)._(r))._(l)._(mod._(sub._(l)._(r))._(r))))); |
|
||||||
|
|
||||||
export const fromNumber: (n: number) => Term = n => !n ? Zero : Succ._(_(() => fromNumber(n - 1))); |
|
||||||
|
|
||||||
export const toNumber: (n: Term) => number = n => $(n._(_(0))._(_l(p => _(({ p }) => _(1 + toNumber(p)))))); |
|
||||||
@ -1,6 +0,0 @@ |
|||||||
import { _, _l, g, n } from './core'; |
|
||||||
import { fromString as _s } from './string'; |
|
||||||
|
|
||||||
n('ord.show'); |
|
||||||
|
|
||||||
export const show = g('show', _l(o => o._(_s('LT'))._(_s('EQ'))._(_s('GT')))); |
|
||||||
@ -1,27 +0,0 @@ |
|||||||
import { $, Term, _, _l, g, n } from './core'; |
|
||||||
import { False, True } from './bool'; |
|
||||||
|
|
||||||
n('ord'); |
|
||||||
|
|
||||||
export const enum NOrd { |
|
||||||
LT, |
|
||||||
EQ, |
|
||||||
GT, |
|
||||||
}; |
|
||||||
|
|
||||||
export const LT = g('LT', _l(lt => _l(_ => _l(_ => lt)))); |
|
||||||
|
|
||||||
export const EQ = g('EQ', _l(_ => _l(eq => _l(_ => eq)))); |
|
||||||
|
|
||||||
export const GT = g('GT', _l(_ => _l(_ => _l(gt => gt)))); |
|
||||||
|
|
||||||
export const eq = g('eq', _l(l => _l(r => l |
|
||||||
._(r._(True)._(False)._(False)) |
|
||||||
._(r._(False)._(True)._(False)) |
|
||||||
._(r._(False)._(False)._(True))))); |
|
||||||
|
|
||||||
export const fromNOrd: (o: NOrd) => Term |
|
||||||
= o => o === NOrd.LT ? LT : o === NOrd.EQ ? EQ : GT; |
|
||||||
|
|
||||||
export const toNOrd: (o: Term) => NOrd |
|
||||||
= o => $(o._(_(NOrd.LT))._(_(NOrd.EQ))._(_(NOrd.GT)).reduce()); |
|
||||||
@ -1,8 +0,0 @@ |
|||||||
import { _, _l, g, n } from './core'; |
|
||||||
import { uncurry } from './pair'; |
|
||||||
import { Nil, concat, cons } from './list'; |
|
||||||
import { fromString as _s } from './string'; |
|
||||||
|
|
||||||
n('pair.show'); |
|
||||||
|
|
||||||
export const show = g('show', _l(fshow => _l(sshow => uncurry._(_l(f => _l(s => concat._(cons._(_s('('))._(cons._(fshow._(f))._(cons._(_s(', '))._(cons._(sshow._(s))._(cons._(_s(')'))._(Nil)))))))))))); |
|
||||||
@ -1,32 +0,0 @@ |
|||||||
import { _, _l, g, n } from './core'; |
|
||||||
import { GT, LT } from './ord'; |
|
||||||
import { False, True, and } from './bool'; |
|
||||||
import { fromString as _s } from './string'; |
|
||||||
|
|
||||||
n('pair'); |
|
||||||
|
|
||||||
export const Pair = g('Pair', _l(f => _l(s => _l(p => p._(f)._(s))))); |
|
||||||
|
|
||||||
export const fst = g('fst', _l(p => p._(_l(f => _l(_ => f))))); |
|
||||||
|
|
||||||
export const snd = g('snd', _l(p => p._(_l(_ => _l(s => s))))); |
|
||||||
|
|
||||||
export const first = g('first', _l(t => _l(p => p._(_l(f => _l(s => Pair._(t._(f))._(s))))))); |
|
||||||
|
|
||||||
export const second = g('second', _l(t => _l(p => p._(_l(f => _l(s => Pair._(f)._(t._(s)))))))); |
|
||||||
|
|
||||||
export const both = g('both', _l(tf => _l(ts => _l(p => p._(_l(f => _l(s => Pair._(tf._(f))._(ts._(s))))))))); |
|
||||||
|
|
||||||
export const uncurry = g('uncurry', _l(t => _l(p => p._(t)))); |
|
||||||
|
|
||||||
export const cmp = g('cmp', _l(fcmp => _l(scmp => uncurry._(_l(lf => _l(ls => uncurry._(_l(rf => _l(rs => fcmp._(lf)._(rf)._(LT)._(scmp._(ls)._(rs))._(GT)))))))))); |
|
||||||
|
|
||||||
export const lt = g('lt', _l(fcmp => _l(scmp => uncurry._(_l(lf => _l(ls => uncurry._(_l(rf => _l(rs => fcmp._(lf)._(rf)._(True)._(scmp._(ls)._(rs)._(True)._(False)._(False))._(False)))))))))); |
|
||||||
|
|
||||||
export const le = g('le', _l(fcmp => _l(scmp => uncurry._(_l(lf => _l(ls => uncurry._(_l(rf => _l(rs => fcmp._(lf)._(rf)._(True)._(scmp._(ls)._(rs)._(True)._(True)._(False))._(False)))))))))); |
|
||||||
|
|
||||||
export const eq = g('eq', _l(feq => _l(seq => uncurry._(_l(lf => _l(ls => uncurry._(_l(rf => _l(rs => and._(feq._(lf)._(rf))._(seq._(ls)._(rs))))))))))); |
|
||||||
|
|
||||||
export const ge = g('ge', _l(fcmp => _l(scmp => uncurry._(_l(lf => _l(ls => uncurry._(_l(rf => _l(rs => fcmp._(lf)._(rf)._(False)._(scmp._(ls)._(rs)._(False)._(True)._(True))._(True)))))))))); |
|
||||||
|
|
||||||
export const gt = g('gt', _l(fcmp => _l(scmp => uncurry._(_l(lf => _l(ls => uncurry._(_l(rf => _l(rs => fcmp._(lf)._(rf)._(False)._(scmp._(ls)._(rs)._(False)._(False)._(True))._(True)))))))))); |
|
||||||
@ -1,7 +0,0 @@ |
|||||||
import { _, g, n } from './core'; |
|
||||||
import { fromChar as _c } from './char'; |
|
||||||
import { fromString as _s, wrap } from './string'; |
|
||||||
|
|
||||||
n('string.show'); |
|
||||||
|
|
||||||
export const show = g('show', wrap._(_s('"'))); |
|
||||||
@ -1,29 +0,0 @@ |
|||||||
import { $, Term, _, _l, g, n } from './core'; |
|
||||||
import { fromChar as _c, cmp as cmpc, eq as eqc, fromChar, toChar } from './char'; |
|
||||||
import { Cons, Nil, append, cmp as cmpl, eq as eql, ge as gel, gt as gtl, le as lel, lt as ltl, nul } from './list'; |
|
||||||
|
|
||||||
export { len, Cons, cons, snoc, append, repeat, iterate, get, update, set } from './list'; |
|
||||||
|
|
||||||
n('string'); |
|
||||||
|
|
||||||
export const Empty = g('Empty', Nil); |
|
||||||
|
|
||||||
export const empty = g('empty', nul); |
|
||||||
|
|
||||||
export const cmp = g('cmp', cmpl._(cmpc)); |
|
||||||
|
|
||||||
export const lt = g('lt', ltl._(cmpc)); |
|
||||||
|
|
||||||
export const le = g('le', lel._(cmpc)); |
|
||||||
|
|
||||||
export const eq = g('eq', eql._(eqc)); |
|
||||||
|
|
||||||
export const ge = g('ge', gel._(cmpc)); |
|
||||||
|
|
||||||
export const gt = g('gt', gtl._(cmpc)); |
|
||||||
|
|
||||||
export const wrap = g('wrap', _l(w => _l(c => append._(w)._(append._(c)._(w))))); |
|
||||||
|
|
||||||
export const fromString: (xs: string) => Term = s => !s.length ? Nil : Cons._(fromChar(s[0]))._(_(() => fromString(s.slice(1)))); |
|
||||||
|
|
||||||
export const toString: (xs: Term) => string = s => $(s._(_(''))._(_l(x => _l(xs => _(({ x, xs }) => _(toChar(x) + toString(xs))))))); |
|
||||||
@ -0,0 +1,160 @@ |
|||||||
|
import { GroupQueueBase, QueueItem } from './data/group-queue'; |
||||||
|
import { List, ListItem } from './data/list'; |
||||||
|
import { Trie } from './data/trie'; |
||||||
|
|
||||||
|
interface InternalStringPairQueueItem extends QueueItem<ListItem<string>> { |
||||||
|
prev?: StringPairQueueItem; |
||||||
|
next?: StringPairQueueItem; |
||||||
|
} |
||||||
|
|
||||||
|
interface StringPairQueueItem extends QueueItem<ListItem<string>> { |
||||||
|
readonly prev?: StringPairQueueItem; |
||||||
|
readonly next?: StringPairQueueItem; |
||||||
|
} |
||||||
|
|
||||||
|
class StringPairQueue extends GroupQueueBase<string, ListItem<string>> { |
||||||
|
protected items: Map<ListItem<string>, StringPairQueueItem>; |
||||||
|
|
||||||
|
public constructor(values?: Iterable<ListItem<string>>) { |
||||||
|
super(); |
||||||
|
this.items = new Map(); |
||||||
|
for (const v of values ?? []) { |
||||||
|
this.insert(v); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected override hash(v: ListItem<string>): string { |
||||||
|
return `${v.value}$$$$${v.next.value}`; |
||||||
|
} |
||||||
|
|
||||||
|
override insert(value: ListItem<string>): StringPairQueueItem { |
||||||
|
const item: StringPairQueueItem = super.insert(value); |
||||||
|
|
||||||
|
const prev: StringPairQueueItem | undefined = this.items.get(value.prev); |
||||||
|
const next: StringPairQueueItem | undefined = this.items.get(value.next); |
||||||
|
(item as InternalStringPairQueueItem).prev = prev; |
||||||
|
(item as InternalStringPairQueueItem).next = next; |
||||||
|
|
||||||
|
if (prev) { |
||||||
|
(prev as InternalStringPairQueueItem).next = item; |
||||||
|
} |
||||||
|
if (next) { |
||||||
|
(next as InternalStringPairQueueItem).prev = item; |
||||||
|
} |
||||||
|
|
||||||
|
this.items.set(value, item); |
||||||
|
|
||||||
|
return item; |
||||||
|
} |
||||||
|
|
||||||
|
override remove(item: StringPairQueueItem): boolean { |
||||||
|
if (!super.remove(item)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if (item.prev) { |
||||||
|
(item.prev as InternalStringPairQueueItem).next = undefined; |
||||||
|
} |
||||||
|
if (item.next) { |
||||||
|
(item.next as InternalStringPairQueueItem).prev = undefined; |
||||||
|
} |
||||||
|
this.items.delete(item.value); |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export type TID = number; |
||||||
|
|
||||||
|
export class Tokenizer { |
||||||
|
protected trie: Trie; |
||||||
|
protected list: string[]; |
||||||
|
|
||||||
|
public get tokens(): Readonly<string[]> { |
||||||
|
return this.list; |
||||||
|
} |
||||||
|
|
||||||
|
protected train(size: number, text: string): void { |
||||||
|
if (!text) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
console.log('Gathering pairs.'); |
||||||
|
|
||||||
|
let log: number = Date.now(); |
||||||
|
const samples: number[] = []; |
||||||
|
|
||||||
|
const tokens = new List(text); |
||||||
|
const pairs: StringPairQueue = new StringPairQueue(tokens.items()); |
||||||
|
const vocab = new Set<string>(tokens); |
||||||
|
|
||||||
|
console.log('Merging pairs.'); |
||||||
|
|
||||||
|
while (vocab.size < size) { |
||||||
|
const begin: number = Date.now(); |
||||||
|
|
||||||
|
const items: List<StringPairQueueItem> | undefined = pairs.peek(); |
||||||
|
if (!items?.length) { |
||||||
|
console.log('No merge found.'); |
||||||
|
break; |
||||||
|
} else { |
||||||
|
const { value: { value: a, next: { value: b } } } = items.first!; |
||||||
|
const merged: string = a + b; |
||||||
|
for (const item of items) { |
||||||
|
if (!item.orphan) { |
||||||
|
if (item.prev) { |
||||||
|
pairs.remove(item.prev); |
||||||
|
} |
||||||
|
if (item.next) { |
||||||
|
pairs.remove(item.next); |
||||||
|
} |
||||||
|
|
||||||
|
pairs.remove(item); |
||||||
|
tokens.set(item.value, merged); |
||||||
|
tokens.delete(item.value.next); |
||||||
|
|
||||||
|
pairs.insert(item.value.prev); |
||||||
|
pairs.insert(item.value); |
||||||
|
} |
||||||
|
} |
||||||
|
vocab.add(merged); |
||||||
|
|
||||||
|
const end: number = Date.now(); |
||||||
|
samples.push(end - begin); |
||||||
|
if (end - log > 5000) { |
||||||
|
console.log(`Merged ${vocab.size} / ${size}, ETA ${Math.round(samples.reduce((a, b) => a + b, 0) / samples.length * (size - vocab.size) / 1000)}s`); |
||||||
|
log = end; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
this.trie = new Trie([...vocab].sort((l, r) => r.length - l.length)); |
||||||
|
} |
||||||
|
|
||||||
|
public constructor(size: number, text: string); |
||||||
|
public constructor(data: object); |
||||||
|
public constructor(sizeOrData: number | object, text?: string) { |
||||||
|
if (text) { |
||||||
|
this.trie = new Trie(); |
||||||
|
this.train(sizeOrData as number, text!); |
||||||
|
} else { |
||||||
|
this.trie = Trie.fromJSON(sizeOrData as object); |
||||||
|
} |
||||||
|
this.list = [...this.trie]; |
||||||
|
} |
||||||
|
|
||||||
|
public tokenize(text: string): TID[] { |
||||||
|
const tokens: string[] = []; |
||||||
|
while (text.length) { |
||||||
|
const match = this.trie.match(text); |
||||||
|
tokens.push(match ?? text[0]); |
||||||
|
text = text.slice(match?.length ?? 1); |
||||||
|
} |
||||||
|
return tokens.map(t => this.list.indexOf(t)); |
||||||
|
} |
||||||
|
|
||||||
|
public detokenize(tids: TID[]): string[] { |
||||||
|
return tids.map(t => this.list[t]); |
||||||
|
} |
||||||
|
|
||||||
|
public toJSON(): object { |
||||||
|
return this.trie.toJSON(); |
||||||
|
} |
||||||
|
} |
||||||
@ -1,48 +0,0 @@ |
|||||||
import { describe, expect, it } from '@jest/globals'; |
|
||||||
import { _ } from '../src/core'; |
|
||||||
import { fromChar as _c } from '../src/char'; |
|
||||||
import { toString as $s, fromString as _s } from '../src/string'; |
|
||||||
import { run } from '../src/bf'; |
|
||||||
|
|
||||||
describe('BF', () => { |
|
||||||
describe('run', () => { |
|
||||||
it('handles output command', () => { |
|
||||||
expect($s(run._(_s('.'))._(_s('')))).toBe(String.fromCharCode(0)); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles input command', () => { |
|
||||||
expect($s(run._(_s(',.'))._(_s('a')))).toBe('a'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles inc command', () => { |
|
||||||
expect($s(run._(_s('+.'))._(_s('')))).toBe(String.fromCharCode(1)); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles dec command', () => { |
|
||||||
expect($s(run._(_s('-.'))._(_s('')))).toBe(String.fromCharCode(255)); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles next command', () => { |
|
||||||
expect($s(run._(_s('+++++>++.'))._(_s('')))).toBe(String.fromCharCode(2)); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles prev command', () => { |
|
||||||
expect($s(run._(_s('+++++>++<+++++.'))._(_s('')))).toBe(String.fromCharCode(10)); |
|
||||||
}); |
|
||||||
|
|
||||||
it('skips loop block on zero', () => { |
|
||||||
expect($s(run._(_s('[+++++].'))._(_s('')))).toBe(String.fromCharCode(0)); |
|
||||||
}); |
|
||||||
|
|
||||||
it('repeats loop block while not zero', () => { |
|
||||||
expect($s(run._(_s('+++++[-].'))._(_s('')))).toBe(String.fromCharCode(0)); |
|
||||||
}); |
|
||||||
|
|
||||||
it('prints "Hello World!"', () => { |
|
||||||
expect($s(run |
|
||||||
._(_s('++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.')) |
|
||||||
._(_s('')))) |
|
||||||
.toBe('Hello World!\n'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
||||||
@ -1,249 +0,0 @@ |
|||||||
import { describe, expect, it } from '@jest/globals'; |
|
||||||
import { undef } from '../src/core'; |
|
||||||
import { toNOrd as $o, NOrd } from '../src/ord'; |
|
||||||
import { toBoolean as $b, False, True, fromBoolean as _b, and, cmp, eq, ge, gt, iif, le, lt, not, or, xor } from '../src/bool'; |
|
||||||
import { toString as $s } from '../src/string'; |
|
||||||
import { show } from '../src/bool.show'; |
|
||||||
|
|
||||||
describe('Bool', () => { |
|
||||||
describe('toBoolean', () => { |
|
||||||
it('converts True', () => { |
|
||||||
expect($b(True)).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('converts False', () => { |
|
||||||
expect($b(False)).toBe(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('fromBoolean', () => { |
|
||||||
it('converts true', () => { |
|
||||||
expect($b(_b(true))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('converts false', () => { |
|
||||||
expect($b(_b(false))).toBe(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('iif', () => { |
|
||||||
it('returns true argument', () => { |
|
||||||
expect($b(iif._(True)._(True)._(False))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('returns false argument', () => { |
|
||||||
expect($b(iif._(False)._(True)._(False))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('ignores other argument', () => { |
|
||||||
expect($b(iif._(True)._(True)._(undef))).toBe(true); |
|
||||||
expect($b(iif._(False)._(undef)._(False))).toBe(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('not', () => { |
|
||||||
it('not False is True', () => { |
|
||||||
expect($b(not._(False))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('not True is False', () => { |
|
||||||
expect($b(not._(True))).toBe(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('lt', () => { |
|
||||||
it('False lt False is False', () => { |
|
||||||
expect($b(lt._(False)._(False))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('False lt True is True', () => { |
|
||||||
expect($b(lt._(False)._(True))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('True lt False is False', () => { |
|
||||||
expect($b(lt._(True)._(False))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('True lt True is False', () => { |
|
||||||
expect($b(lt._(True)._(True))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('True lt ignores second argument', () => { |
|
||||||
expect($b(lt._(True)._(undef))).toBe(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('le', () => { |
|
||||||
it('False le False is True', () => { |
|
||||||
expect($b(le._(False)._(False))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('False le True is True', () => { |
|
||||||
expect($b(le._(False)._(True))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('True le False is False', () => { |
|
||||||
expect($b(le._(True)._(False))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('True le True is True', () => { |
|
||||||
expect($b(le._(True)._(True))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('False le ignores second argument', () => { |
|
||||||
expect($b(le._(False)._(undef))).toBe(true); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('eq', () => { |
|
||||||
it('False eq False is True', () => { |
|
||||||
expect($b(eq._(False)._(False))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('False eq True is False', () => { |
|
||||||
expect($b(eq._(False)._(True))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('True eq False is False', () => { |
|
||||||
expect($b(eq._(True)._(False))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('True eq True is True', () => { |
|
||||||
expect($b(eq._(True)._(True))).toBe(true); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('ge', () => { |
|
||||||
it('False ge False is True', () => { |
|
||||||
expect($b(ge._(False)._(False))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('False ge True is False', () => { |
|
||||||
expect($b(ge._(False)._(True))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('True ge False is True', () => { |
|
||||||
expect($b(ge._(True)._(False))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('True ge True is True', () => { |
|
||||||
expect($b(ge._(True)._(True))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('True ge ignores second argument', () => { |
|
||||||
expect($b(ge._(True)._(undef))).toBe(true); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('gt', () => { |
|
||||||
it('False gt False is False', () => { |
|
||||||
expect($b(gt._(False)._(False))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('False gt True is False', () => { |
|
||||||
expect($b(gt._(False)._(True))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('True gt False is True', () => { |
|
||||||
expect($b(gt._(True)._(False))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('True gt True is False', () => { |
|
||||||
expect($b(gt._(True)._(True))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('False gt ignores second argument', () => { |
|
||||||
expect($b(gt._(False)._(undef))).toBe(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('cmp', () => { |
|
||||||
it('False cmp False is EQ', () => { |
|
||||||
expect($o(cmp._(False)._(False))).toBe(NOrd.EQ); |
|
||||||
}); |
|
||||||
|
|
||||||
it('False cmp True is LT', () => { |
|
||||||
expect($o(cmp._(False)._(True))).toBe(NOrd.LT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('True cmp False is GT', () => { |
|
||||||
expect($o(cmp._(True)._(False))).toBe(NOrd.GT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('True cmp True is EQ', () => { |
|
||||||
expect($o(cmp._(True)._(True))).toBe(NOrd.EQ); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('and', () => { |
|
||||||
it('False and False is False', () => { |
|
||||||
expect($b(and._(False)._(False))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('False and True is False', () => { |
|
||||||
expect($b(and._(False)._(True))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('True and False is False', () => { |
|
||||||
expect($b(and._(True)._(False))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('True and True is True', () => { |
|
||||||
expect($b(and._(True)._(True))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('False and ignores other argument', () => { |
|
||||||
expect($b(and._(False)._(undef))).toBe(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('or', () => { |
|
||||||
it('False or False is False', () => { |
|
||||||
expect($b(or._(False)._(False))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('False or True is True', () => { |
|
||||||
expect($b(or._(False)._(True))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('True or False is True', () => { |
|
||||||
expect($b(or._(True)._(False))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('True or True is True', () => { |
|
||||||
expect($b(or._(True)._(True))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('True or ignores other argument', () => { |
|
||||||
expect($b(or._(True)._(undef))).toBe(true); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('xor', () => { |
|
||||||
it('False xor False is False', () => { |
|
||||||
expect($b(xor._(False)._(False))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('False xor True is True', () => { |
|
||||||
expect($b(xor._(False)._(True))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('True xor False is True', () => { |
|
||||||
expect($b(xor._(True)._(False))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('True xor True is False', () => { |
|
||||||
expect($b(xor._(True)._(True))).toBe(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('show', () => { |
|
||||||
it('show False is "False"', () => { |
|
||||||
expect($s(show._(False))).toBe('False'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('show True is "True"', () => { |
|
||||||
expect($s(show._(True))).toBe('True'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
||||||
@ -1,476 +0,0 @@ |
|||||||
import { describe, expect, it } from '@jest/globals'; |
|
||||||
import { undef } from '../src/core'; |
|
||||||
import { toNOrd as $o, NOrd } from '../src/ord'; |
|
||||||
import { toBoolean as $b, fromBoolean as _b } from '../src/bool'; |
|
||||||
import { toNumber as $n, Inc, fromNumber as _n, cmp, dec, div, eq, ge, gt, ifz, inc, le, lt, mod, mul, sub, sum, x00 } from '../src/byte'; |
|
||||||
import { toString as $s } from '../src/string'; |
|
||||||
import { show } from '../src/byte.show'; |
|
||||||
|
|
||||||
describe('Byte', () => { |
|
||||||
describe('toNumber', () => { |
|
||||||
it('converts 0', () => { |
|
||||||
expect($n(x00)).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('converts 1', () => { |
|
||||||
expect($n(Inc._(x00))).toBe(1); |
|
||||||
}); |
|
||||||
|
|
||||||
it('converts 2', () => { |
|
||||||
expect($n(Inc._(Inc._(x00)))).toBe(2); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('fromNumber', () => { |
|
||||||
it('converts 0', () => { |
|
||||||
expect($n(_n(0))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('converts 1', () => { |
|
||||||
expect($n(_n(1))).toBe(1); |
|
||||||
}); |
|
||||||
|
|
||||||
it('converts 2', () => { |
|
||||||
expect($n(_n(2))).toBe(2); |
|
||||||
}); |
|
||||||
|
|
||||||
it('converts 300', () => { |
|
||||||
expect($n(_n(300))).toBe(44); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('ifz', () => { |
|
||||||
it('returns zero branch', () => { |
|
||||||
expect($b(ifz._(_n(0))._(_b(true))._(_b(false)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('returns non-zero branch', () => { |
|
||||||
expect($b(ifz._(_n(1))._(_b(true))._(_b(false)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('returns non-zero branch', () => { |
|
||||||
expect($b(ifz._(_n(10))._(_b(true))._(_b(false)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('skips other branch', () => { |
|
||||||
expect($b(ifz._(_n(0))._(_b(true))._(undef))).toBe(true); |
|
||||||
expect($b(ifz._(_n(1))._(undef)._(_b(false)))).toBe(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('inc', () => { |
|
||||||
it('inc 0 is 1', () => { |
|
||||||
expect($n(inc._(_n(0)))).toBe(1); |
|
||||||
}); |
|
||||||
|
|
||||||
it('inc 1 is 2', () => { |
|
||||||
expect($n(inc._(_n(1)))).toBe(2); |
|
||||||
}); |
|
||||||
|
|
||||||
it('inc 10 is 11', () => { |
|
||||||
expect($n(inc._(_n(10)))).toBe(11); |
|
||||||
}); |
|
||||||
|
|
||||||
it('inc 255 is 0', () => { |
|
||||||
expect($n(inc._(_n(255)))).toBe(0); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('dec', () => { |
|
||||||
it('dec 0 is 255', () => { |
|
||||||
expect($n(dec._(_n(0)))).toBe(255); |
|
||||||
}); |
|
||||||
|
|
||||||
it('dec 1 is 0', () => { |
|
||||||
expect($n(dec._(_n(1)))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('dec 10 is 9', () => { |
|
||||||
expect($n(dec._(_n(10)))).toBe(9); |
|
||||||
}); |
|
||||||
|
|
||||||
it('dec 255 is 254', () => { |
|
||||||
expect($n(dec._(_n(255)))).toBe(254); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('cmp', () => { |
|
||||||
it('0 cmp 0 is EQ', () => { |
|
||||||
expect($o(cmp._(_n(0))._(_n(0)))).toBe(NOrd.EQ); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 cmp 1 is LT', () => { |
|
||||||
expect($o(cmp._(_n(0))._(_n(1)))).toBe(NOrd.LT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('1 cmp 0 is GT', () => { |
|
||||||
expect($o(cmp._(_n(1))._(_n(0)))).toBe(NOrd.GT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('3 cmp 3 is EQ', () => { |
|
||||||
expect($o(cmp._(_n(3))._(_n(3)))).toBe(NOrd.EQ); |
|
||||||
}); |
|
||||||
|
|
||||||
it('3 cmp 7 is LT', () => { |
|
||||||
expect($o(cmp._(_n(3))._(_n(7)))).toBe(NOrd.LT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('7 cmp 3 is GT', () => { |
|
||||||
expect($o(cmp._(_n(7))._(_n(3)))).toBe(NOrd.GT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 cmp 255 is LT', () => { |
|
||||||
expect($o(cmp._(_n(0))._(_n(255)))).toBe(NOrd.LT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('255 cmp 0 is GT', () => { |
|
||||||
expect($o(cmp._(_n(255))._(_n(0)))).toBe(NOrd.GT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('255 cmp 255 is EQ', () => { |
|
||||||
expect($o(cmp._(_n(255))._(_n(255)))).toBe(NOrd.EQ); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('lt', () => { |
|
||||||
it('0 lt 0 is False', () => { |
|
||||||
expect($b(lt._(_n(0))._(_n(0)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 lt 1 is True', () => { |
|
||||||
expect($b(lt._(_n(0))._(_n(1)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('1 lt 0 is False', () => { |
|
||||||
expect($b(lt._(_n(1))._(_n(0)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('3 lt 3 is False', () => { |
|
||||||
expect($b(lt._(_n(3))._(_n(3)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('3 lt 7 is True', () => { |
|
||||||
expect($b(lt._(_n(3))._(_n(7)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('7 lt 3 is False', () => { |
|
||||||
expect($b(lt._(_n(7))._(_n(3)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 lt 255 is True', () => { |
|
||||||
expect($b(lt._(_n(0))._(_n(255)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('255 lt 0 is False', () => { |
|
||||||
expect($b(lt._(_n(255))._(_n(0)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('255 lt 255 is False', () => { |
|
||||||
expect($b(lt._(_n(255))._(_n(255)))).toBe(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('le', () => { |
|
||||||
it('0 le 0 is True', () => { |
|
||||||
expect($b(le._(_n(0))._(_n(0)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 le 1 is True', () => { |
|
||||||
expect($b(le._(_n(0))._(_n(1)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('1 le 0 is False', () => { |
|
||||||
expect($b(le._(_n(1))._(_n(0)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('3 le 3 is True', () => { |
|
||||||
expect($b(le._(_n(3))._(_n(3)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('3 le 7 is True', () => { |
|
||||||
expect($b(le._(_n(3))._(_n(7)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('7 le 3 is False', () => { |
|
||||||
expect($b(le._(_n(7))._(_n(3)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 le 255 is True', () => { |
|
||||||
expect($b(le._(_n(0))._(_n(255)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('255 le 0 is False', () => { |
|
||||||
expect($b(le._(_n(255))._(_n(0)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('255 le 255 is True', () => { |
|
||||||
expect($b(le._(_n(255))._(_n(255)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 le skips second argument', () => { |
|
||||||
expect($b(le._(_n(0))._(undef))).toBe(true); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('eq', () => { |
|
||||||
it('0 eq 0 is True', () => { |
|
||||||
expect($b(eq._(_n(0))._(_n(0)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 eq 1 is False', () => { |
|
||||||
expect($b(eq._(_n(0))._(_n(1)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('1 eq 0 is False', () => { |
|
||||||
expect($b(eq._(_n(1))._(_n(0)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('3 eq 3 is True', () => { |
|
||||||
expect($b(eq._(_n(3))._(_n(3)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('3 eq 7 is False', () => { |
|
||||||
expect($b(eq._(_n(3))._(_n(7)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('7 eq 3 is False', () => { |
|
||||||
expect($b(eq._(_n(7))._(_n(3)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 eq 255 is False', () => { |
|
||||||
expect($b(eq._(_n(0))._(_n(255)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('255 eq 0 is False', () => { |
|
||||||
expect($b(eq._(_n(255))._(_n(0)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('255 eq 255 is True', () => { |
|
||||||
expect($b(eq._(_n(255))._(_n(255)))).toBe(true); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('ge', () => { |
|
||||||
it('0 ge 0 is True', () => { |
|
||||||
expect($b(ge._(_n(0))._(_n(0)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 ge 1 is False', () => { |
|
||||||
expect($b(ge._(_n(0))._(_n(1)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('1 ge 0 is True', () => { |
|
||||||
expect($b(ge._(_n(1))._(_n(0)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('3 ge 3 is True', () => { |
|
||||||
expect($b(ge._(_n(3))._(_n(3)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('3 ge 7 is False', () => { |
|
||||||
expect($b(ge._(_n(3))._(_n(7)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('7 ge 3 is True', () => { |
|
||||||
expect($b(ge._(_n(7))._(_n(3)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 ge 255 is False', () => { |
|
||||||
expect($b(ge._(_n(0))._(_n(255)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('255 ge 0 is True', () => { |
|
||||||
expect($b(ge._(_n(255))._(_n(0)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('255 ge 255 is True', () => { |
|
||||||
expect($b(ge._(_n(255))._(_n(255)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('ge 0 skips first argument', () => { |
|
||||||
expect($b(ge._(undef)._(_n(0)))).toBe(true); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('gt', () => { |
|
||||||
it('0 gt 0 is False', () => { |
|
||||||
expect($b(gt._(_n(0))._(_n(0)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 gt 1 is False', () => { |
|
||||||
expect($b(gt._(_n(0))._(_n(1)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('1 gt 0 is True', () => { |
|
||||||
expect($b(gt._(_n(1))._(_n(0)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('3 gt 3 is False', () => { |
|
||||||
expect($b(gt._(_n(3))._(_n(3)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('3 gt 7 is False', () => { |
|
||||||
expect($b(gt._(_n(3))._(_n(7)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('7 gt 3 is True', () => { |
|
||||||
expect($b(gt._(_n(7))._(_n(3)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 gt 255 is False', () => { |
|
||||||
expect($b(gt._(_n(0))._(_n(255)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('255 gt 0 is True', () => { |
|
||||||
expect($b(gt._(_n(255))._(_n(0)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('255 gt 255 is False', () => { |
|
||||||
expect($b(gt._(_n(255))._(_n(255)))).toBe(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('sum', () => { |
|
||||||
it('0 sum 0 is 0', () => { |
|
||||||
expect($n(sum._(_n(0))._(_n(0)))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 sum 1 is 1', () => { |
|
||||||
expect($n(sum._(_n(0))._(_n(1)))).toBe(1); |
|
||||||
}); |
|
||||||
|
|
||||||
it('1 sum 0 is 1', () => { |
|
||||||
expect($n(sum._(_n(1))._(_n(0)))).toBe(1); |
|
||||||
}); |
|
||||||
|
|
||||||
it('3 sum 4 is 7', () => { |
|
||||||
expect($n(sum._(_n(3))._(_n(4)))).toBe(7); |
|
||||||
}); |
|
||||||
|
|
||||||
it('200 sum 200 is 144', () => { |
|
||||||
expect($n(sum._(_n(200))._(_n(200)))).toBe(144); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('sub', () => { |
|
||||||
it('0 sub 0 is 0', () => { |
|
||||||
expect($n(sub._(_n(0))._(_n(0)))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 sub 1 is 255', () => { |
|
||||||
expect($n(sub._(_n(0))._(_n(1)))).toBe(255); |
|
||||||
}); |
|
||||||
|
|
||||||
it('1 sub 0 is 1', () => { |
|
||||||
expect($n(sub._(_n(1))._(_n(0)))).toBe(1); |
|
||||||
}); |
|
||||||
|
|
||||||
it('7 sub 4 is 3', () => { |
|
||||||
expect($n(sub._(_n(7))._(_n(4)))).toBe(3); |
|
||||||
}); |
|
||||||
|
|
||||||
it('4 sub 7 is 253', () => { |
|
||||||
expect($n(sub._(_n(4))._(_n(7)))).toBe(253); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('mul', () => { |
|
||||||
it('0 mul 0 is 0', () => { |
|
||||||
expect($n(mul._(_n(0))._(_n(0)))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 mul 10 is 0', () => { |
|
||||||
expect($n(mul._(_n(0))._(_n(10)))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('10 mul 0 is 0', () => { |
|
||||||
expect($n(mul._(_n(1))._(_n(0)))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('1 mul 10 is 10', () => { |
|
||||||
expect($n(mul._(_n(1))._(_n(10)))).toBe(10); |
|
||||||
}); |
|
||||||
|
|
||||||
it('10 mul 1 is 10', () => { |
|
||||||
expect($n(mul._(_n(10))._(_n(1)))).toBe(10); |
|
||||||
}); |
|
||||||
|
|
||||||
it('3 mul 4 is 12', () => { |
|
||||||
expect($n(mul._(_n(3))._(_n(4)))).toBe(12); |
|
||||||
}); |
|
||||||
|
|
||||||
it('100 mul 100 is 12', () => { |
|
||||||
expect($n(mul._(_n(3))._(_n(4)))).toBe(12); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 mul ignores second argument', () => { |
|
||||||
expect($n(mul._(_n(0))._(undef))).toBe(0); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('div', () => { |
|
||||||
it('0 div 10 is 0', () => { |
|
||||||
expect($n(div._(_n(0))._(_n(10)))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('1 div 10 is 0', () => { |
|
||||||
expect($n(div._(_n(1))._(_n(10)))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('10 div 1 is 10', () => { |
|
||||||
expect($n(div._(_n(10))._(_n(1)))).toBe(10); |
|
||||||
}); |
|
||||||
|
|
||||||
it('10 div 5 is 2', () => { |
|
||||||
expect($n(div._(_n(10))._(_n(5)))).toBe(2); |
|
||||||
}); |
|
||||||
|
|
||||||
it('10 div 3 is 3', () => { |
|
||||||
expect($n(div._(_n(10))._(_n(3)))).toBe(3); |
|
||||||
}); |
|
||||||
|
|
||||||
it.failing('0 div ignores second argument', () => { |
|
||||||
expect($n(div._(_n(0))._(undef))).toBe(0); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('mod', () => { |
|
||||||
it('0 mod 10 is 0', () => { |
|
||||||
expect($n(mod._(_n(0))._(_n(10)))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('1 mod 10 is 1', () => { |
|
||||||
expect($n(mod._(_n(1))._(_n(10)))).toBe(1); |
|
||||||
}); |
|
||||||
|
|
||||||
it('10 mod 1 is 0', () => { |
|
||||||
expect($n(mod._(_n(10))._(_n(1)))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('10 mod 5 is 0', () => { |
|
||||||
expect($n(mod._(_n(10))._(_n(5)))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('10 mod 3 is 1', () => { |
|
||||||
expect($n(mod._(_n(10))._(_n(3)))).toBe(1); |
|
||||||
}); |
|
||||||
|
|
||||||
it.failing('0 mod ignores second argument', () => { |
|
||||||
expect($n(mod._(_n(0))._(undef))).toBe(0); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('show', () => { |
|
||||||
it('show 0 is "0"', () => { |
|
||||||
expect($s(show._(_n(0)))).toBe('0'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('show 1 is "1"', () => { |
|
||||||
expect($s(show._(_n(1)))).toBe('1'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('show 255 is "255"', () => { |
|
||||||
expect($s(show._(_n(255)))).toBe('255'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
||||||
@ -1,215 +0,0 @@ |
|||||||
import { describe, expect, it } from '@jest/globals'; |
|
||||||
import { toNOrd as $o, NOrd } from '../src/ord'; |
|
||||||
import { toBoolean as $b, fromBoolean as _b } from '../src/bool'; |
|
||||||
import { fromNumber as _n } from '../src/byte'; |
|
||||||
import { toChar as $c, fromChar as _c, cmp, eq, ge, gt, le, lt } from '../src/char'; |
|
||||||
import { toString as $s } from '../src/string'; |
|
||||||
import { show } from '../src/char.show'; |
|
||||||
|
|
||||||
describe('Char', () => { |
|
||||||
describe('toChar', () => { |
|
||||||
it('converts "0"', () => { |
|
||||||
expect($c(_n(48))).toBe('0'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('converts "a"', () => { |
|
||||||
expect($c(_n(97))).toBe('a'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('converts "Z"', () => { |
|
||||||
expect($c(_n(90))).toBe('Z'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('converts "-"', () => { |
|
||||||
expect($c(_n(45))).toBe('-'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('fromChar', () => { |
|
||||||
it('converts "0"', () => { |
|
||||||
expect($c(_c('0'))).toBe('0'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('converts "a"', () => { |
|
||||||
expect($c(_c('a'))).toBe('a'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('converts "Z"', () => { |
|
||||||
expect($c(_c('Z'))).toBe('Z'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('converts "-"', () => { |
|
||||||
expect($c(_c('-'))).toBe('-'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('cmp', () => { |
|
||||||
it('"0" cmp "0" is EQ', () => { |
|
||||||
expect($o(cmp._(_c('0'))._(_c('0')))).toBe(NOrd.EQ); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"0" cmp "a" is LT', () => { |
|
||||||
expect($o(cmp._(_c('0'))._(_c('a')))).toBe(NOrd.LT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"a" cmp "0" is GT', () => { |
|
||||||
expect($o(cmp._(_c('a'))._(_c('0')))).toBe(NOrd.GT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"z" cmp "z" is EQ', () => { |
|
||||||
expect($o(cmp._(_c('z'))._(_c('z')))).toBe(NOrd.EQ); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"z" cmp "-" is GT', () => { |
|
||||||
expect($o(cmp._(_c('z'))._(_c('-')))).toBe(NOrd.GT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"-" cmp "z" is LT', () => { |
|
||||||
expect($o(cmp._(_c('-'))._(_c('z')))).toBe(NOrd.LT); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('lt', () => { |
|
||||||
it('"0" lt "0" is False', () => { |
|
||||||
expect($b(lt._(_c('0'))._(_c('0')))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"0" lt "a" is True', () => { |
|
||||||
expect($b(lt._(_c('0'))._(_c('a')))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"a" lt "0" is False', () => { |
|
||||||
expect($b(lt._(_c('a'))._(_c('0')))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"z" lt "z" is False', () => { |
|
||||||
expect($b(lt._(_c('z'))._(_c('z')))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"z" lt "-" is False', () => { |
|
||||||
expect($b(lt._(_c('z'))._(_c('-')))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"-" lt "z" is True', () => { |
|
||||||
expect($b(lt._(_c('-'))._(_c('z')))).toBe(true); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('le', () => { |
|
||||||
it('"0" le "0" is True', () => { |
|
||||||
expect($b(le._(_c('0'))._(_c('0')))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"0" le "a" is True', () => { |
|
||||||
expect($b(le._(_c('0'))._(_c('a')))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"a" le "0" is False', () => { |
|
||||||
expect($b(le._(_c('a'))._(_c('0')))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"z" le "z" is True', () => { |
|
||||||
expect($b(le._(_c('z'))._(_c('z')))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"z" le "-" is False', () => { |
|
||||||
expect($b(le._(_c('z'))._(_c('-')))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"-" le "z" is True', () => { |
|
||||||
expect($b(le._(_c('-'))._(_c('z')))).toBe(true); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('eq', () => { |
|
||||||
it('"0" eq "0" is True', () => { |
|
||||||
expect($b(eq._(_c('0'))._(_c('0')))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"0" eq "a" is False', () => { |
|
||||||
expect($b(eq._(_c('0'))._(_c('a')))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"a" eq "0" is False', () => { |
|
||||||
expect($b(eq._(_c('a'))._(_c('0')))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"z" eq "z" is True', () => { |
|
||||||
expect($b(eq._(_c('z'))._(_c('z')))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"z" eq "-" is False', () => { |
|
||||||
expect($b(eq._(_c('z'))._(_c('-')))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"-" eq "z" is False', () => { |
|
||||||
expect($b(eq._(_c('-'))._(_c('z')))).toBe(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('ge', () => { |
|
||||||
it('"0" ge "0" is True', () => { |
|
||||||
expect($b(ge._(_c('0'))._(_c('0')))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"0" ge "a" is False', () => { |
|
||||||
expect($b(ge._(_c('0'))._(_c('a')))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"a" ge "0" is True', () => { |
|
||||||
expect($b(ge._(_c('a'))._(_c('0')))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"z" ge "z" is True', () => { |
|
||||||
expect($b(ge._(_c('z'))._(_c('z')))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"z" ge "-" is True', () => { |
|
||||||
expect($b(ge._(_c('z'))._(_c('-')))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"-" ge "z" is False', () => { |
|
||||||
expect($b(ge._(_c('-'))._(_c('z')))).toBe(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('gt', () => { |
|
||||||
it('"0" gt "0" is False', () => { |
|
||||||
expect($b(gt._(_c('0'))._(_c('0')))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"0" gt "a" is False', () => { |
|
||||||
expect($b(gt._(_c('0'))._(_c('a')))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"a" gt "0" is True', () => { |
|
||||||
expect($b(gt._(_c('a'))._(_c('0')))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"z" gt "z" is False', () => { |
|
||||||
expect($b(gt._(_c('z'))._(_c('z')))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"z" gt "-" is True', () => { |
|
||||||
expect($b(gt._(_c('z'))._(_c('-')))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"-" gt "z" is False', () => { |
|
||||||
expect($b(gt._(_c('-'))._(_c('z')))).toBe(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('show', () => { |
|
||||||
it('show "0" is "\'0\'"', () => { |
|
||||||
expect($s(show._(_c('0')))).toBe('\'0\''); |
|
||||||
}); |
|
||||||
|
|
||||||
it('show "a" is "\'a\'"', () => { |
|
||||||
expect($s(show._(_c('a')))).toBe('\'a\''); |
|
||||||
}); |
|
||||||
|
|
||||||
it('show "-" is "\'-\'"', () => { |
|
||||||
expect($s(show._(_c('-')))).toBe('\'-\''); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
||||||
@ -1,24 +0,0 @@ |
|||||||
import { describe, expect, it } from '@jest/globals'; |
|
||||||
import { $, _, cnst, id, undef } from '../src/core'; |
|
||||||
|
|
||||||
describe('id', () => { |
|
||||||
it('returns first argument', () => { |
|
||||||
expect($(id._(_('first')))).toBe('first'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('cnst', () => { |
|
||||||
it('returns first argument after receiving second', () => { |
|
||||||
expect($(cnst._(_('first'))._(_('second')))).toBe('first'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('ignores second argument', () => { |
|
||||||
expect($(cnst._(_('first'))._(undef))).toBe('first'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('pipe', () => { |
|
||||||
it.failing('applies both functions in order', () => { |
|
||||||
expect('Not implemented').toBe('Implemented'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
@ -0,0 +1,57 @@ |
|||||||
|
import { describe, expect, it } from '@jest/globals'; |
||||||
|
import { GroupQueue, QueueItem } from '../src/data/group-queue'; |
||||||
|
import { List } from '../src/data/list'; |
||||||
|
|
||||||
|
describe('GroupQueue', () => { |
||||||
|
function json<T>(list: List<QueueItem<T>>): T[] { |
||||||
|
return [...list].map(({ value }) => value); |
||||||
|
} |
||||||
|
|
||||||
|
describe('from array', () => { |
||||||
|
it('[]', () => expect([...new GroupQueue([])].map(json)).toEqual([])); |
||||||
|
it('[1]', () => expect([...new GroupQueue([1])].map(json)).toEqual([[1]])); |
||||||
|
it('["b", "c", "b", "c"]', () => expect([...new GroupQueue(['b', 'c', 'b', 'c'])].map(json)).toEqual([['b', 'b'], ['c', 'c']])); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('length', () => { |
||||||
|
it('[]', () => expect(new GroupQueue([]).length).toEqual(0)); |
||||||
|
it('[1]', () => expect(new GroupQueue([1]).length).toEqual(1)); |
||||||
|
it('["b", "c", "b", "c"]', () => expect(new GroupQueue(['b', 'c', 'b', 'c']).length).toEqual(2)); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('insert', () => { |
||||||
|
function insert<T>(queue: GroupQueue<T>, value: T): T[][] { |
||||||
|
queue.insert(value); |
||||||
|
return [...queue].map(json); |
||||||
|
} |
||||||
|
it('0 -> []', () => expect([...insert(new GroupQueue<number>([]), 0)]).toEqual([[0]])); |
||||||
|
it('0 -> [1]', () => expect([...insert(new GroupQueue([1]), 0)]).toEqual([[1], [0]])); |
||||||
|
it('1 -> [1]', () => expect([...insert(new GroupQueue([1]), 1)]).toEqual([[1, 1]])); |
||||||
|
it('"d" -> ["b", "c", "b", "c"]', () => expect([...insert(new GroupQueue(['b', 'c', 'b', 'c']), 'd')]).toEqual([['b', 'b'], ['c', 'c'], ['d']])); |
||||||
|
it('"b" -> ["b", "c", "b", "c"]', () => expect([...insert(new GroupQueue(['b', 'c', 'b', 'c']), 'b')]).toEqual([['b', 'b', 'b'], ['c', 'c']])); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('itemOf / remove', () => { |
||||||
|
function remove<T>(queue: GroupQueue<T>, value: T): T[][] { |
||||||
|
const item = queue.itemOf(value); |
||||||
|
if (item) { |
||||||
|
queue.remove(item); |
||||||
|
} |
||||||
|
return [...queue].map(json); |
||||||
|
} |
||||||
|
it('[] - 0', () => expect([...remove(new GroupQueue<number>([]), 0)]).toEqual([])); |
||||||
|
it('[1] - 0', () => expect([...remove(new GroupQueue([1]), 0)]).toEqual([[1]])); |
||||||
|
it('[1] - 1', () => expect([...remove(new GroupQueue([1]), 1)]).toEqual([])); |
||||||
|
it('["b", "c", "b", "c"] - "d"', () => expect([...remove(new GroupQueue(['b', 'c', 'b', 'c']), 'd')]).toEqual([['b', 'b'], ['c', 'c']])); |
||||||
|
it('["b", "c", "b", "c"] - "b"', () => expect([...remove(new GroupQueue(['b', 'c', 'b', 'c']), 'b')]).toEqual([['c', 'c'], ['b']])); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('pop', () => { |
||||||
|
function pop<T>(queue: GroupQueue<T>): [T[], T[][]] { |
||||||
|
return [[...queue.pop() ?? []].map(({ value }) => value), [...queue].map(json)]; |
||||||
|
} |
||||||
|
it('[]', () => expect([...pop(new GroupQueue<number>([]))]).toEqual([[], []])); |
||||||
|
it('[1]', () => expect([...pop(new GroupQueue([1]))]).toEqual([[1], []])); |
||||||
|
it('["b", "c", "b", "c"]', () => expect([...pop(new GroupQueue(['b', 'c', 'b', 'c']))]).toEqual([['b', 'b'], [['c', 'c']]])); |
||||||
|
}); |
||||||
|
}); |
||||||
@ -1,726 +1,88 @@ |
|||||||
import { describe, expect, it } from '@jest/globals'; |
import { describe, expect, it } from '@jest/globals'; |
||||||
import { $, Term, _, _l, g, n, undef } from '../src/core'; |
import { List, ListItem } from '../src/data/list'; |
||||||
import { toNOrd as $o, NOrd } from '../src/ord'; |
|
||||||
import { toBoolean as $b, iif } from '../src/bool'; |
|
||||||
import { toNumber as $n, Succ, Zero, fromNumber as _n, fromNumber as _nn, cmp as cmpn, eq as eqn, even, gt as ngt, odd, succ, sum } from '../src/num'; |
|
||||||
import { toArray as $a, Cons, Nil, fromArray as _a, all, any, append, cmp, concat, cons, eq, filter, foldl, foldlz, foldr, foldrz, ge, get, gt, head, intersperse, iterate, le, len, lt, map, nul, set, singleton, snoc, tail, update } from '../src/list'; |
|
||||||
import { toChar as $c } from '../src/char'; |
|
||||||
import { toString as $s } from '../src/string'; |
|
||||||
import { show as nshow } from '../src/num.show'; |
|
||||||
import { show } from '../src/list.show'; |
|
||||||
|
|
||||||
n('list.spec'); |
|
||||||
|
|
||||||
describe('List', () => { |
describe('List', () => { |
||||||
const infinite: Term = g('infinite', iterate._(Succ)._(Zero)); |
describe('from / to array', () => { |
||||||
|
it('[]', () => expect([...new List([])]).toEqual([])); |
||||||
const _na: (xs: number[]) => Term = xs => !xs.length ? Nil : Cons._(_n(xs[0]))._(_(() => _na(xs.slice(1)))); |
it('[1]', () => expect([...new List([1])]).toEqual([1])); |
||||||
|
it('["b", "c", "b", "c"]', () => expect([...new List(['b', 'c', 'b', 'c'])]).toEqual(['b', 'c', 'b', 'c'])); |
||||||
const $na: (xs: Term) => number[] = xs => $(xs._(_([]))._(_l(x => _l(xs => _(({ x, xs }) => _([$n(x), ...$na(xs)])))))); |
|
||||||
|
|
||||||
describe('toArray', () => { |
|
||||||
it('converts []', () => { |
|
||||||
expect($a(Nil)).toEqual([]); |
|
||||||
}); |
|
||||||
|
|
||||||
it('converts [0]', () => { |
|
||||||
expect($a(Cons._(_n(0))._(Nil)).map($n)).toEqual([0]); |
|
||||||
}); |
|
||||||
|
|
||||||
it('converts [0, 1]', () => { |
|
||||||
expect($a(Cons._(_n(0))._(Cons._(_n(1))._(Nil))).map($n)).toEqual([0, 1]); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('fromArray', () => { |
|
||||||
it('converts []', () => { |
|
||||||
expect($a(_a([]))).toEqual([]); |
|
||||||
}); |
|
||||||
|
|
||||||
it('converts [0]', () => { |
|
||||||
expect($a(_a([0].map(_n))).map($n)).toEqual([0]); |
|
||||||
}); |
|
||||||
|
|
||||||
it('converts 2', () => { |
|
||||||
expect($a(_a([0, 1].map(_n))).map($n)).toEqual([0, 1]); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('cons', () => { |
|
||||||
it('0 cons Nil is [0]', () => { |
|
||||||
expect($na(cons._(_n(0))._(Nil))).toEqual([0]); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 cons [1] is [0, 1]', () => { |
|
||||||
expect($na(cons._(_n(0))._(_na([1])))).toEqual([0, 1]); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 cons [1, 2] is [0, 1, 2]', () => { |
|
||||||
expect($na(cons._(_n(0))._(_na([1, 2])))).toEqual([0, 1, 2]); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite lists', () => { |
|
||||||
expect($n(cons._(_n(0))._(infinite)._(undef)._(_l(x => _l(_ => x))))).toBe(0); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('snoc', () => { |
|
||||||
it('Nil snoc 0 is [0]', () => { |
|
||||||
expect($na(snoc._(Nil)._(_n(0)))).toEqual([0]); |
|
||||||
}); |
}); |
||||||
|
|
||||||
it('[1] snoc 0 is [1, 0]', () => { |
describe('length', () => { |
||||||
expect($na(snoc._(_na([1]))._(_n(0)))).toEqual([1, 0]); |
it('[]', () => expect(new List([]).length).toEqual(0)); |
||||||
}); |
it('[1]', () => expect(new List([1]).length).toEqual(1)); |
||||||
|
it('["b", "c", "b", "c"]', () => expect(new List(['b', 'c', 'b', 'c']).length).toEqual(4)); |
||||||
it('[1, 2] snoc 0 is [1, 2, 0]', () => { |
|
||||||
expect($na(snoc._(_na([1, 2]))._(_n(0)))).toEqual([1, 2, 0]); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite lists', () => { |
|
||||||
expect($n(snoc._(infinite)._(_n(0))._(undef)._(_l(x => _l(_ => x))))).toBe(0); |
|
||||||
}); |
|
||||||
}); |
}); |
||||||
|
|
||||||
describe('nul', () => { |
describe('first', () => { |
||||||
it('nul Nil is True', () => { |
it('[]', () => expect(new List([]).first).toBeUndefined()); |
||||||
expect($b(nul._(_na([])))).toEqual(true); |
it('[1]', () => expect(new List([1]).first).toEqual(1)); |
||||||
|
it('["b", "c", "b", "c"]', () => expect(new List(['b', 'c', 'b', 'c']).first).toEqual('b')); |
||||||
}); |
}); |
||||||
|
|
||||||
it('nul [1] is False', () => { |
describe('last', () => { |
||||||
expect($b(nul._(_na([1])))).toEqual(false); |
it('[]', () => expect(new List([]).last).toBeUndefined()); |
||||||
|
it('[1]', () => expect(new List([1]).last).toEqual(1)); |
||||||
|
it('["b", "c", "b", "c"]', () => expect(new List(['b', 'c', 'b', 'c']).last).toEqual('c')); |
||||||
}); |
}); |
||||||
|
|
||||||
it('nul [1, 2] is False', () => { |
describe('prepend', () => { |
||||||
expect($b(nul._(_na([1, 2])))).toEqual(false); |
function prepend<T>(list: List<T>, value: T): List<T> { |
||||||
}); |
list.prepend(value); |
||||||
|
return list; |
||||||
it('handles infinite lists', () => { |
} |
||||||
expect($b(nul._(infinite))).toEqual(false); |
it('0 -> []', () => expect([...prepend(new List<number>([]), 0)]).toEqual([0])); |
||||||
}); |
it('0 -> [1]', () => expect([...prepend(new List([1]), 0)]).toEqual([0, 1])); |
||||||
}); |
it('"d" -> ["b", "c", "b", "c"]', () => expect([...prepend(new List(['b', 'c', 'b', 'c']), 'd')]).toEqual(['d', 'b', 'c', 'b', 'c'])); |
||||||
|
|
||||||
describe('len', () => { |
|
||||||
it('len Nil is 0', () => { |
|
||||||
expect($n(len._(_na([])))).toEqual(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('len [1] is 1', () => { |
|
||||||
expect($n(len._(_na([1])))).toEqual(1); |
|
||||||
}); |
|
||||||
|
|
||||||
it('len [1, 2] is 2', () => { |
|
||||||
expect($n(len._(_na([1, 2])))).toEqual(2); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite lists', () => { |
|
||||||
expect($b(ngt._(len._(infinite))._(Zero))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('ignores items', () => { |
|
||||||
expect($n(len._(cons._(undef)._(cons._(undef)._(Nil))))).toEqual(2); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('singleton', () => { |
|
||||||
it('singleton 0 is [0]', () => { |
|
||||||
expect($na(singleton._(_n(0)))).toEqual([0]); |
|
||||||
}); |
|
||||||
}); |
}); |
||||||
|
|
||||||
describe('append', () => { |
describe('append', () => { |
||||||
it('Nil append Nil is Nil', () => { |
function append<T>(list: List<T>, value: T): List<T> { |
||||||
expect($na(append._(Nil)._(Nil))).toEqual([]); |
list.append(value); |
||||||
}); |
return list; |
||||||
|
} |
||||||
it('[0] append [1] is [0, 1]', () => { |
it('[] <- 0', () => expect([...append(new List<number>([]), 0)]).toEqual([0])); |
||||||
expect($na(append._(_na([0]))._(_na([1])))).toEqual([0, 1]); |
it('[1] <- 0', () => expect([...append(new List([1]), 0)]).toEqual([1, 0])); |
||||||
}); |
it('["b", "c", "b", "c"] <- "d"', () => expect([...append(new List(['b', 'c', 'b', 'c']), 'd')]).toEqual(['b', 'c', 'b', 'c', 'd'])); |
||||||
|
}); |
||||||
it('[0, 1] append [2, 3] is [0, 1, 2, 3]', () => { |
|
||||||
expect($na(append._(_na([0, 1]))._(_na([2, 3])))).toEqual([0, 1, 2, 3]); |
describe('includes', () => { |
||||||
}); |
it('[] not includes 0', () => expect(new List<number>([]).includes(0)).toBe(false)); |
||||||
|
it('[1] not includes 0', () => expect(new List([1]).includes(0)).toBe(false)); |
||||||
it('handles infinite lists', () => { |
it('[1] includes 1', () => expect(new List([1]).includes(1)).toBe(true)); |
||||||
expect($n(append._(infinite)._(infinite)._(undef)._(_l(x => _l(_ => x))))).toBe(0); |
it('["b", "c", "b", "c"] not includes "d"', () => expect(new List(['b', 'c', 'b', 'c']).includes('d')).toBe(false)); |
||||||
}); |
it('["b", "c", "b", "c"] includes "b"', () => expect(new List(['b', 'c', 'b', 'c']).includes('b')).toBe(true)); |
||||||
}); |
}); |
||||||
|
|
||||||
describe('concat', () => { |
describe('itemOf / set', () => { |
||||||
it('concat [Nil, Nil, Nil] is Nil', () => { |
function update<T>(list: List<T>, from: T, to: T): List<T> { |
||||||
expect($na(concat._(cons._(Nil)._(cons._(Nil)._(cons._(Nil)._(Nil)))))).toEqual([]); |
const item: ListItem<T> | undefined = list.itemOf(from); |
||||||
}); |
if (item) { |
||||||
|
list.set(item, to); |
||||||
it('concat [[0], [1], [2]] is [0, 1, 2]', () => { |
} |
||||||
expect($na(concat._(cons._(_na([0]))._(cons._(_na([1]))._(cons._(_na([2]))._(Nil)))))).toEqual([0, 1, 2]); |
return list; |
||||||
}); |
} |
||||||
|
|
||||||
it('concat [[0, 1], [2, 3], [4, 5]] is [0, 1, 2, 3, 4, 5]', () => { |
it('[]: 0 -> 1', () => expect([...update(new List<number>([]), 0, 1)]).toEqual([])); |
||||||
expect($na(concat._(cons._(_na([0, 1]))._(cons._(_na([2, 3]))._(cons._(_na([4, 5]))._(Nil)))))).toEqual([0, 1, 2, 3, 4, 5]); |
it('[1]: 0 -> 1', () => expect([...update(new List([1]), 0, 1)]).toEqual([1])); |
||||||
}); |
it('[1]: 1 -> 0', () => expect([...update(new List([1]), 1, 0)]).toEqual([0])); |
||||||
|
it('["b", "c", "b", "c"]: "b" -> "d"', () => expect([...update(new List(['b', 'c', 'b', 'c']), 'b', 'd')]).toEqual(['d', 'c', 'b', 'c'])); |
||||||
it('handles infinite lists', () => { |
it('["b", "c", "b", "c"]: "d" -> "b"', () => expect([...update(new List(['b', 'c', 'b', 'c']), 'd', 'b')]).toEqual(['b', 'c', 'b', 'c'])); |
||||||
const dinfinite: Term = g('dinfinite', _(() => cons._(infinite)._(dinfinite))); |
}); |
||||||
expect($n(concat._(dinfinite)._(undef)._(_l(x => _l(_ => x))))).toBe(0); |
|
||||||
}); |
describe('itemOf / delete', () => { |
||||||
}); |
function remove<T>(list: List<T>, value: T): List<T> { |
||||||
|
const item: ListItem<T> | undefined = list.itemOf(value); |
||||||
// describe('repeat', () => {
|
if (item) { |
||||||
// it('repeat 0 is [0, 0, 0, ...]', () => {
|
list.delete(item); |
||||||
// expect($na(
|
} |
||||||
// repeat._(_n(0))<List<Num>>(l((x0: Num) => l((x1s: List<Num>) =>
|
return list; |
||||||
// x1s<List<Num>>(l((x1: Num) => l((x2s: List<Num>) =>
|
} |
||||||
// x2s<List<Num>>(l((x2: Num) => l((_xrs: List<Num>): List<Num> => cons._(x0)._(cons._(x1)._(cons._(x2)._(Nil)))))))))))))).toEqual([0, 0, 0]);
|
|
||||||
// });
|
it('[] - 0', () => expect([...remove(new List<number>([]), 0)]).toEqual([])); |
||||||
// });
|
it('[1] - 0', () => expect([...remove(new List([1]), 0)]).toEqual([1])); |
||||||
|
it('[1] - 1', () => expect([...remove(new List([1]), 1)]).toEqual([])); |
||||||
// describe('iterate', () => {
|
it('["b", "c", "b", "c"] - "b"', () => expect([...remove(new List(['b', 'c', 'b', 'c']), 'b')]).toEqual(['c', 'b', 'c'])); |
||||||
// it('iterate succ 0 is [0, 1, 2, ...]', () => {
|
it('["b", "c", "b", "c"] - "d"', () => expect([...remove(new List(['b', 'c', 'b', 'c']), 'd')]).toEqual(['b', 'c', 'b', 'c'])); |
||||||
// expect($na(
|
|
||||||
// iterate._(_n(0))<List<Num>>(l((x0: Num) => l((x1s: List<Num>) =>
|
|
||||||
// x1s<List<Num>>(l((x1: Num) => l((x2s: List<Num>) =>
|
|
||||||
// x2s<List<Num>>(l((x2: Num) => l((_xrs: List<Num>): List<Num> => cons._(x0)._(cons._(x1)._(cons._(x2)._(Nil)))))))))))))).toEqual([0, 1, 2]);
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
describe('head', () => { |
|
||||||
it('returns nothing from Nil', () => { |
|
||||||
expect(() => $(head._(Nil))).toThrow(); |
|
||||||
}); |
|
||||||
|
|
||||||
it('returns only item', () => { |
|
||||||
expect($n(head._(_na([0])))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('returns first item', () => { |
|
||||||
expect($n(head._(_na([0, 1])))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite lists', () => { |
|
||||||
expect($n(head._(infinite))).toBe(0); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('tail', () => { |
|
||||||
it('drops nothing from Nil', () => { |
|
||||||
expect(() => $na(tail._(Nil))).toThrow(); |
|
||||||
}); |
|
||||||
|
|
||||||
it('drops only item', () => { |
|
||||||
expect($na(tail._(_na([0])))).toEqual([]); |
|
||||||
}); |
|
||||||
|
|
||||||
it('drops first item', () => { |
|
||||||
expect($na(tail._(_na([0, 1])))).toEqual([1]); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite lists', () => { |
|
||||||
expect($n(head._(tail._(infinite)))).toBe(1); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('foldl', () => { |
|
||||||
it('folds Nil', () => { |
|
||||||
expect($n(foldl._(sum)._(Zero)._(_na([])))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('folds single item', () => { |
|
||||||
expect($n(foldl._(sum)._(Zero)._(_na([1])))).toBe(1); |
|
||||||
}); |
|
||||||
|
|
||||||
it('folds several items', () => { |
|
||||||
expect($n(foldl._(sum)._(Zero)._(_na([1, 2])))).toBe(3); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('foldlz', () => { |
|
||||||
it('does not fold Nil', () => { |
|
||||||
expect(() => $n(foldlz._(sum)._(Nil))).toThrow(); |
|
||||||
}); |
|
||||||
|
|
||||||
it('folds single item', () => { |
|
||||||
expect($n(foldlz._(sum)._(_na([1])))).toBe(1); |
|
||||||
}); |
|
||||||
|
|
||||||
it('folds several items', () => { |
|
||||||
expect($n(foldlz._(sum)._(_na([1, 2])))).toBe(3); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('foldr', () => { |
|
||||||
it('folds Nil', () => { |
|
||||||
expect($n(foldr._(sum)._(Zero)._(_na([])))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('folds single item', () => { |
|
||||||
expect($n(foldr._(sum)._(Zero)._(_na([1])))).toBe(1); |
|
||||||
}); |
|
||||||
|
|
||||||
it('folds several items', () => { |
|
||||||
expect($n(foldr._(sum)._(Zero)._(_na([1, 2])))).toBe(3); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite lists', () => { |
|
||||||
expect($n( |
|
||||||
foldr |
|
||||||
._(_l(x => _l(a => sum._(x)._(iif._(eqn._(_n(5))._(x))._(Zero)._(a))))) |
|
||||||
._(Zero) |
|
||||||
._(infinite))) |
|
||||||
.toBe(15); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('foldrz', () => { |
|
||||||
it('does not fold Nil', () => { |
|
||||||
expect(() => $n(foldrz._(sum)._(Nil))).toThrow(); |
|
||||||
}); |
|
||||||
|
|
||||||
it.failing('folds single item', () => { |
|
||||||
expect($n(foldrz._(sum)._(_na([1])))).toBe(1); |
|
||||||
}); |
|
||||||
|
|
||||||
it.failing('folds several items', () => { |
|
||||||
expect($n(foldrz._(sum)._(_na([1, 2])))).toBe(3); |
|
||||||
}); |
|
||||||
|
|
||||||
it.failing('handles infinite lists', () => { |
|
||||||
expect($n( |
|
||||||
foldrz |
|
||||||
._(_l(x => _l(a => sum._(x)._(iif._(eqn._(_n(5))._(x))._(Zero)._(a))))) |
|
||||||
._(infinite))) |
|
||||||
.toBe(15); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('cmp', () => { |
|
||||||
it('Nil cmp Nil is EQ', () => { |
|
||||||
expect($o(cmp._(cmpn)._(_na([]))._(_na([])))).toEqual(NOrd.EQ); |
|
||||||
}); |
|
||||||
|
|
||||||
it('Nil cmp [0, 1, 2] is LT', () => { |
|
||||||
expect($o(cmp._(cmpn)._(_na([]))._(_na([0, 1, 2])))).toEqual(NOrd.LT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2] cmp Nil is GT', () => { |
|
||||||
expect($o(cmp._(cmpn)._(_na([0, 1, 2]))._(_na([])))).toEqual(NOrd.GT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0] cmp [0, 1, 2] is LT', () => { |
|
||||||
expect($o(cmp._(cmpn)._(_na([0]))._(_na([0, 1, 2])))).toEqual(NOrd.LT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2] cmp [0] is GT', () => { |
|
||||||
expect($o(cmp._(cmpn)._(_na([0, 1, 2]))._(_na([0])))).toEqual(NOrd.GT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[1, 2, 0] cmp [0, 1, 2] is GT', () => { |
|
||||||
expect($o(cmp._(cmpn)._(_na([1, 2, 0]))._(_na([0, 1, 2])))).toEqual(NOrd.GT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2] cmp [1, 2, 0] is LT', () => { |
|
||||||
expect($o(cmp._(cmpn)._(_na([0, 1, 2]))._(_na([1, 2, 0])))).toEqual(NOrd.LT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2] cmp [0, 1, 2] is EQ', () => { |
|
||||||
expect($o(cmp._(cmpn)._(_na([0, 1, 2]))._(_na([0, 1, 2])))).toEqual(NOrd.EQ); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite lists', () => { |
|
||||||
expect($o(cmp._(cmpn)._(_na([0, 1, 2]))._(infinite))).toEqual(NOrd.LT); |
|
||||||
expect($o(cmp._(cmpn)._(infinite)._(_na([0, 1, 2])))).toEqual(NOrd.GT); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('lt', () => { |
|
||||||
it('Nil lt Nil is False', () => { |
|
||||||
expect($b(lt._(cmpn)._(_na([]))._(_na([])))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('Nil lt [0, 1, 2] is True', () => { |
|
||||||
expect($b(lt._(cmpn)._(_na([]))._(_na([0, 1, 2])))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2] lt Nil is False', () => { |
|
||||||
expect($b(lt._(cmpn)._(_na([0, 1, 2]))._(_na([])))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0] lt [0, 1, 2] is True', () => { |
|
||||||
expect($b(lt._(cmpn)._(_na([0]))._(_na([0, 1, 2])))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2] lt [0] is False', () => { |
|
||||||
expect($b(lt._(cmpn)._(_na([0, 1, 2]))._(_na([0])))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[1, 2, 0] lt [0, 1, 2] is False', () => { |
|
||||||
expect($b(lt._(cmpn)._(_na([1, 2, 0]))._(_na([0, 1, 2])))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2] lt [1, 2, 0] is True', () => { |
|
||||||
expect($b(lt._(cmpn)._(_na([0, 1, 2]))._(_na([1, 2, 0])))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2] lt [0, 1, 2] is False', () => { |
|
||||||
expect($b(lt._(cmpn)._(_na([0, 1, 2]))._(_na([0, 1, 2])))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite lists', () => { |
|
||||||
expect($b(lt._(cmpn)._(_na([0, 1, 2]))._(infinite))).toEqual(true); |
|
||||||
expect($b(lt._(cmpn)._(infinite)._(_na([0, 1, 2])))).toEqual(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('le', () => { |
|
||||||
it('Nil le Nil is True', () => { |
|
||||||
expect($b(le._(cmpn)._(_na([]))._(_na([])))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('Nil le [0, 1, 2] is True', () => { |
|
||||||
expect($b(le._(cmpn)._(_na([]))._(_na([0, 1, 2])))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2] le Nil is False', () => { |
|
||||||
expect($b(le._(cmpn)._(_na([0, 1, 2]))._(_na([])))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0] le [0, 1, 2] is True', () => { |
|
||||||
expect($b(le._(cmpn)._(_na([0]))._(_na([0, 1, 2])))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2] le [0] is False', () => { |
|
||||||
expect($b(le._(cmpn)._(_na([0, 1, 2]))._(_na([0])))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[1, 2, 0] le [0, 1, 2] is False', () => { |
|
||||||
expect($b(le._(cmpn)._(_na([1, 2, 0]))._(_na([0, 1, 2])))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2] le [1, 2, 0] is True', () => { |
|
||||||
expect($b(le._(cmpn)._(_na([0, 1, 2]))._(_na([1, 2, 0])))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2] le [0, 1, 2] is True', () => { |
|
||||||
expect($b(le._(cmpn)._(_na([0, 1, 2]))._(_na([0, 1, 2])))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite lists', () => { |
|
||||||
expect($b(le._(cmpn)._(_na([0, 1, 2]))._(infinite))).toEqual(true); |
|
||||||
expect($b(le._(cmpn)._(infinite)._(_na([0, 1, 2])))).toEqual(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('eq', () => { |
|
||||||
it('Nil eq Nil is True', () => { |
|
||||||
expect($b(eq._(eqn)._(_na([]))._(_na([])))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('Nil eq [0, 1, 2] is False', () => { |
|
||||||
expect($b(eq._(eqn)._(_na([]))._(_na([0, 1, 2])))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2] eq Nil is False', () => { |
|
||||||
expect($b(eq._(eqn)._(_na([0, 1, 2]))._(_na([])))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0] eq [0, 1, 2] is False', () => { |
|
||||||
expect($b(eq._(eqn)._(_na([0]))._(_na([0, 1, 2])))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2] eq [0] is False', () => { |
|
||||||
expect($b(eq._(eqn)._(_na([0, 1, 2]))._(_na([0])))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[1, 2, 0] eq [0, 1, 2] is False', () => { |
|
||||||
expect($b(eq._(eqn)._(_na([1, 2, 0]))._(_na([0, 1, 2])))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2] eq [1, 2, 0] is False', () => { |
|
||||||
expect($b(eq._(eqn)._(_na([0, 1, 2]))._(_na([1, 2, 0])))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2] eq [0, 1, 2] is True', () => { |
|
||||||
expect($b(eq._(eqn)._(_na([0, 1, 2]))._(_na([0, 1, 2])))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite lists', () => { |
|
||||||
expect($b(eq._(eqn)._(_na([0, 1, 2]))._(infinite))).toEqual(false); |
|
||||||
expect($b(eq._(eqn)._(infinite)._(_na([0, 1, 2])))).toEqual(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('ge', () => { |
|
||||||
it('Nil ge Nil is True', () => { |
|
||||||
expect($b(ge._(cmpn)._(_na([]))._(_na([])))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('Nil ge [0, 1, 2] is False', () => { |
|
||||||
expect($b(ge._(cmpn)._(_na([]))._(_na([0, 1, 2])))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2] ge Nil is True', () => { |
|
||||||
expect($b(ge._(cmpn)._(_na([0, 1, 2]))._(_na([])))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0] ge [0, 1, 2] is False', () => { |
|
||||||
expect($b(ge._(cmpn)._(_na([0]))._(_na([0, 1, 2])))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2] ge [0] is True', () => { |
|
||||||
expect($b(ge._(cmpn)._(_na([0, 1, 2]))._(_na([0])))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[1, 2, 0] ge [0, 1, 2] is True', () => { |
|
||||||
expect($b(ge._(cmpn)._(_na([1, 2, 0]))._(_na([0, 1, 2])))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2] ge [1, 2, 0] is False', () => { |
|
||||||
expect($b(ge._(cmpn)._(_na([0, 1, 2]))._(_na([1, 2, 0])))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2] ge [0, 1, 2] is True', () => { |
|
||||||
expect($b(ge._(cmpn)._(_na([0, 1, 2]))._(_na([0, 1, 2])))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite lists', () => { |
|
||||||
expect($b(ge._(cmpn)._(_na([0, 1, 2]))._(infinite))).toEqual(false); |
|
||||||
expect($b(ge._(cmpn)._(infinite)._(_na([0, 1, 2])))).toEqual(true); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('gt', () => { |
|
||||||
it('Nil gt Nil is False', () => { |
|
||||||
expect($b(gt._(cmpn)._(_na([]))._(_na([])))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('Nil gt [0, 1, 2] is False', () => { |
|
||||||
expect($b(gt._(cmpn)._(_na([]))._(_na([0, 1, 2])))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2] gt Nil is True', () => { |
|
||||||
expect($b(gt._(cmpn)._(_na([0, 1, 2]))._(_na([])))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0] gt [0, 1, 2] is False', () => { |
|
||||||
expect($b(gt._(cmpn)._(_na([0]))._(_na([0, 1, 2])))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2] gt [0] is True', () => { |
|
||||||
expect($b(gt._(cmpn)._(_na([0, 1, 2]))._(_na([0])))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[1, 2, 0] gt [0, 1, 2] is True', () => { |
|
||||||
expect($b(gt._(cmpn)._(_na([1, 2, 0]))._(_na([0, 1, 2])))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2] gt [1, 2, 0] is False', () => { |
|
||||||
expect($b(gt._(cmpn)._(_na([0, 1, 2]))._(_na([1, 2, 0])))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2] gt [0, 1, 2] is False', () => { |
|
||||||
expect($b(gt._(cmpn)._(_na([0, 1, 2]))._(_na([0, 1, 2])))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite lists', () => { |
|
||||||
expect($b(gt._(cmpn)._(_na([0, 1, 2]))._(infinite))).toEqual(false); |
|
||||||
expect($b(gt._(cmpn)._(infinite)._(_na([0, 1, 2])))).toEqual(true); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('map', () => { |
|
||||||
it('maps Nil', () => { |
|
||||||
expect($na(map._(succ)._(_na([])))).toEqual([]); |
|
||||||
}); |
|
||||||
|
|
||||||
it('maps only item', () => { |
|
||||||
expect($na(map._(succ)._(_na([0])))).toEqual([1]); |
|
||||||
}); |
|
||||||
|
|
||||||
it('maps several items', () => { |
|
||||||
expect($na(map._(succ)._(_na([0, 1])))).toEqual([1, 2]); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite lists', () => { |
|
||||||
expect($n(head._(map._(succ)._(infinite)))).toBe(1); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('filter', () => { |
|
||||||
it('filter Nil', () => { |
|
||||||
expect($na(filter._(odd)._(_na([])))).toEqual([]); |
|
||||||
}); |
|
||||||
|
|
||||||
it('filters only item', () => { |
|
||||||
expect($na(filter._(odd)._(_na([])))).toEqual([]); |
|
||||||
}); |
|
||||||
|
|
||||||
it('filters several items', () => { |
|
||||||
expect($na(filter._(odd)._(_na([0, 1])))).toEqual([1]); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite lists', () => { |
|
||||||
expect($n(head._(filter._(odd)._(infinite)))).toBe(1); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('any', () => { |
|
||||||
it('any Nil is False', () => { |
|
||||||
expect($b(any._(odd)._(_na([])))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('any for no matching item is False', () => { |
|
||||||
expect($b(any._(odd)._(_na([0, 2])))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('any for matching item is True', () => { |
|
||||||
expect($b(any._(odd)._(_na([0, 1, 2])))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite lists', () => { |
|
||||||
expect($b(any._(odd)._(infinite))).toEqual(true); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('all', () => { |
|
||||||
it('all Nil is True', () => { |
|
||||||
expect($b(all._(even)._(_na([])))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('all for non-matching item is False', () => { |
|
||||||
expect($b(all._(even)._(_na([0, 1, 2])))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('all for no non-matching item is True', () => { |
|
||||||
expect($b(all._(even)._(_na([0, 2])))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite lists', () => { |
|
||||||
expect($b(all._(even)._(infinite))).toEqual(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('get', () => { |
|
||||||
it('returns nothing from Nil', () => { |
|
||||||
expect(() => $n(get._(_n(0))._(Nil))).toThrow(); |
|
||||||
}); |
|
||||||
|
|
||||||
it('returns nothing with out of bounds index', () => { |
|
||||||
expect(() => $n(get._(_n(100))._(_na([0, 1, 2, 3])))).toThrow(); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2, 3][0] is 0', () => { |
|
||||||
expect($n(get._(_n(0))._(_na([0, 1, 2, 3])))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2, 3][1] is 1', () => { |
|
||||||
expect($n(get._(_n(1))._(_na([0, 1, 2, 3])))).toBe(1); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2, 3][2] is 2', () => { |
|
||||||
expect($n(get._(_n(2))._(_na([0, 1, 2, 3])))).toBe(2); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2, 3][3] get 3 is 3', () => { |
|
||||||
expect($n(get._(_n(3))._(_na([0, 1, 2, 3])))).toBe(3); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite lists', () => { |
|
||||||
expect($n(get._(_n(4))._(infinite))).toBe(4); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('set', () => { |
|
||||||
it('can not update Nil', () => { |
|
||||||
expect(() => $na(set._(_n(10))._(_n(0))._(Nil))).toThrow(); |
|
||||||
}); |
|
||||||
|
|
||||||
it('can not update out of bounds index', () => { |
|
||||||
expect(() => $na(set._(_n(10))._(_n(100))._(_na([0, 1, 2, 3])))).toThrow(); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2, 3][0] = 10 is [10, 1, 2, 3]', () => { |
|
||||||
expect($na(set._(_n(10))._(_n(0))._(_na([0, 1, 2, 3])))).toEqual([10, 1, 2, 3]); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2, 3][1] = 10 is [0, 10, 2, 3]', () => { |
|
||||||
expect($na(set._(_n(10))._(_n(1))._(_na([0, 1, 2, 3])))).toEqual([0, 10, 2, 3]); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2, 3][2] = 10 is [0, 1, 10, 3]', () => { |
|
||||||
expect($na(set._(_n(10))._(_n(2))._(_na([0, 1, 2, 3])))).toEqual([0, 1, 10, 3]); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2, 3][3] = 10 is [0, 1, 2, 10]', () => { |
|
||||||
expect($na(set._(_n(10))._(_n(3))._(_na([0, 1, 2, 3])))).toEqual([0, 1, 2, 10]); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite lists', () => { |
|
||||||
expect($n(get._(_n(4))._(set._(_n(10))._(_n(4))._(infinite)))).toBe(10); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('update', () => { |
|
||||||
it('can not update Nil', () => { |
|
||||||
expect(() => $na(update._(succ)._(_n(0))._(Nil))).toThrow(); |
|
||||||
}); |
|
||||||
|
|
||||||
it('can not update out of bounds index', () => { |
|
||||||
expect(() => $na(update._(succ)._(_n(100))._(_na([0, 1, 2, 3])))).toThrow(); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2, 3][0]++ is [10, 1, 2, 3]', () => { |
|
||||||
expect($na(update._(succ)._(_n(0))._(_na([0, 1, 2, 3])))).toEqual([1, 1, 2, 3]); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2, 3][1]++ is [0, 10, 2, 3]', () => { |
|
||||||
expect($na(update._(succ)._(_n(1))._(_na([0, 1, 2, 3])))).toEqual([0, 2, 2, 3]); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2, 3][2]++ is [0, 1, 10, 3]', () => { |
|
||||||
expect($na(update._(succ)._(_n(2))._(_na([0, 1, 2, 3])))).toEqual([0, 1, 3, 3]); |
|
||||||
}); |
|
||||||
|
|
||||||
it('[0, 1, 2, 3][3]++ is [0, 1, 2, 10]', () => { |
|
||||||
expect($na(update._(succ)._(_n(3))._(_na([0, 1, 2, 3])))).toEqual([0, 1, 2, 4]); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite lists', () => { |
|
||||||
expect($n(get._(_n(4))._(update._(succ)._(_n(4))._(infinite)))).toBe(5); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('intersperse', () => { |
|
||||||
it('0 intersperse Nil is Nil', () => { |
|
||||||
expect($na(intersperse._(_n(0))._(Nil))).toEqual([]); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 intersperse [1] is [1]', () => { |
|
||||||
expect($na(intersperse._(_n(0))._(_na([1])))).toEqual([1]); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 intersperse [1, 2, 3] is [1, 0, 2, 0, 3]', () => { |
|
||||||
expect($na(intersperse._(_n(0))._(_na([1, 2, 3])))).toEqual([1, 0, 2, 0, 3]); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite lists', () => { |
|
||||||
expect($n(head._(intersperse._(_n(0))._(infinite)))).toBe(0); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('show', () => { |
|
||||||
it('show Nil is "[]"', () => { |
|
||||||
expect($s(show._(nshow)._(_na([])))).toBe('[]'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('show [0] is "[0]"', () => { |
|
||||||
expect($s(show._(nshow)._(_na([0])))).toBe('[0]'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('show [0, 1, 2] is "[0, 1, 2]"', () => { |
|
||||||
expect($s(show._(nshow)._(_na([0, 1, 2])))).toBe('[0, 1, 2]'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite lists', () => { |
|
||||||
expect($c(head._(show._(nshow)._(infinite)))).toBe('['); |
|
||||||
}); |
|
||||||
}); |
}); |
||||||
}); |
}); |
||||||
|
|||||||
@ -1,409 +0,0 @@ |
|||||||
import { describe, expect, it } from '@jest/globals'; |
|
||||||
import { Term, _, g, n, undef } from '../src/core'; |
|
||||||
import { toNOrd as $o, NOrd } from '../src/ord'; |
|
||||||
import { toBoolean as $b, fromBoolean as _b } from '../src/bool'; |
|
||||||
import { toNumber as $n, Succ, Zero, fromNumber as _n, cmp, div, eq, ge, gt, ifz, le, lt, mod, mul, pred, sub, succ, sum } from '../src/num'; |
|
||||||
import { toString as $s } from '../src/string'; |
|
||||||
import { show } from '../src/num.show'; |
|
||||||
|
|
||||||
n('num.spec'); |
|
||||||
|
|
||||||
describe('Num', () => { |
|
||||||
const infinity: Term = g('infinity', _(() => Succ._(infinity))); |
|
||||||
|
|
||||||
describe('toNumber', () => { |
|
||||||
it('converts 0', () => { |
|
||||||
expect($n(Zero)).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('converts 1', () => { |
|
||||||
expect($n(Succ._(Zero))).toBe(1); |
|
||||||
}); |
|
||||||
|
|
||||||
it('converts 2', () => { |
|
||||||
expect($n(Succ._(Succ._(Zero)))).toBe(2); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('fromNumber', () => { |
|
||||||
it('converts 0', () => { |
|
||||||
expect($n(_n(0))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('converts 1', () => { |
|
||||||
expect($n(_n(1))).toBe(1); |
|
||||||
}); |
|
||||||
|
|
||||||
it('converts 2', () => { |
|
||||||
expect($n(_n(2))).toBe(2); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('ifz', () => { |
|
||||||
it('returns zero branch', () => { |
|
||||||
expect($b(ifz._(_n(0))._(_b(true))._(_b(false)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('returns non-zero branch', () => { |
|
||||||
expect($b(ifz._(_n(1))._(_b(true))._(_b(false)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('returns non-zero branch', () => { |
|
||||||
expect($b(ifz._(_n(10))._(_b(true))._(_b(false)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinity', () => { |
|
||||||
expect($b(ifz._(infinity)._(_b(true))._(_b(false)))).toBe(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('succ', () => { |
|
||||||
it('succ 0 is 1', () => { |
|
||||||
expect($n(succ._(_n(0)))).toBe(1); |
|
||||||
}); |
|
||||||
|
|
||||||
it('succ 1 is 2', () => { |
|
||||||
expect($n(succ._(_n(1)))).toBe(2); |
|
||||||
}); |
|
||||||
|
|
||||||
it('succ 10 is 11', () => { |
|
||||||
expect($n(succ._(_n(10)))).toBe(11); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('pred', () => { |
|
||||||
it('pred 0 is 0', () => { |
|
||||||
expect($n(pred._(_n(0)))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('pred 1 is 0', () => { |
|
||||||
expect($n(pred._(_n(1)))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('pred 10 is 9', () => { |
|
||||||
expect($n(pred._(_n(10)))).toBe(9); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('cmp', () => { |
|
||||||
it('0 cmp 0 is EQ', () => { |
|
||||||
expect($o(cmp._(_n(0))._(_n(0)))).toBe(NOrd.EQ); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 cmp 1 is LT', () => { |
|
||||||
expect($o(cmp._(_n(0))._(_n(1)))).toBe(NOrd.LT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('1 cmp 0 is GT', () => { |
|
||||||
expect($o(cmp._(_n(1))._(_n(0)))).toBe(NOrd.GT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('3 cmp 3 is EQ', () => { |
|
||||||
expect($o(cmp._(_n(3))._(_n(3)))).toBe(NOrd.EQ); |
|
||||||
}); |
|
||||||
|
|
||||||
it('3 cmp 7 is LT', () => { |
|
||||||
expect($o(cmp._(_n(3))._(_n(7)))).toBe(NOrd.LT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('7 cmp 3 is GT', () => { |
|
||||||
expect($o(cmp._(_n(7))._(_n(3)))).toBe(NOrd.GT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinity', () => { |
|
||||||
expect($o(cmp._(_n(7))._(infinity))).toBe(NOrd.LT); |
|
||||||
expect($o(cmp._(infinity)._(_n(7)))).toBe(NOrd.GT); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('lt', () => { |
|
||||||
it('0 lt 0 is False', () => { |
|
||||||
expect($b(lt._(_n(0))._(_n(0)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 lt 1 is True', () => { |
|
||||||
expect($b(lt._(_n(0))._(_n(1)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('1 lt 0 is False', () => { |
|
||||||
expect($b(lt._(_n(1))._(_n(0)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('3 lt 3 is False', () => { |
|
||||||
expect($b(lt._(_n(3))._(_n(3)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('3 lt 7 is True', () => { |
|
||||||
expect($b(lt._(_n(3))._(_n(7)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('7 lt 3 is False', () => { |
|
||||||
expect($b(lt._(_n(7))._(_n(3)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinity', () => { |
|
||||||
expect($b(lt._(_n(7))._(infinity))).toBe(true); |
|
||||||
expect($b(lt._(infinity)._(_n(7)))).toBe(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('le', () => { |
|
||||||
it('0 le 0 is True', () => { |
|
||||||
expect($b(le._(_n(0))._(_n(0)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 le 1 is True', () => { |
|
||||||
expect($b(le._(_n(0))._(_n(1)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('1 le 0 is False', () => { |
|
||||||
expect($b(le._(_n(1))._(_n(0)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('3 le 3 is True', () => { |
|
||||||
expect($b(le._(_n(3))._(_n(3)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('3 le 7 is True', () => { |
|
||||||
expect($b(le._(_n(3))._(_n(7)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('7 le 3 is False', () => { |
|
||||||
expect($b(le._(_n(7))._(_n(3)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinity', () => { |
|
||||||
expect($b(le._(_n(7))._(infinity))).toBe(true); |
|
||||||
expect($b(le._(infinity)._(_n(7)))).toBe(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('eq', () => { |
|
||||||
it('0 eq 0 is True', () => { |
|
||||||
expect($b(eq._(_n(0))._(_n(0)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 eq 1 is False', () => { |
|
||||||
expect($b(eq._(_n(0))._(_n(1)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('1 eq 0 is False', () => { |
|
||||||
expect($b(eq._(_n(1))._(_n(0)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('3 eq 3 is True', () => { |
|
||||||
expect($b(eq._(_n(3))._(_n(3)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('3 eq 7 is False', () => { |
|
||||||
expect($b(eq._(_n(3))._(_n(7)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('7 eq 3 is False', () => { |
|
||||||
expect($b(eq._(_n(7))._(_n(3)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinity', () => { |
|
||||||
expect($b(eq._(_n(7))._(infinity))).toBe(false); |
|
||||||
expect($b(eq._(infinity)._(_n(7)))).toBe(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('ge', () => { |
|
||||||
it('0 ge 0 is True', () => { |
|
||||||
expect($b(ge._(_n(0))._(_n(0)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 ge 1 is False', () => { |
|
||||||
expect($b(ge._(_n(0))._(_n(1)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('1 ge 0 is True', () => { |
|
||||||
expect($b(ge._(_n(1))._(_n(0)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('3 ge 3 is True', () => { |
|
||||||
expect($b(ge._(_n(3))._(_n(3)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('3 ge 7 is False', () => { |
|
||||||
expect($b(ge._(_n(3))._(_n(7)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('7 ge 3 is True', () => { |
|
||||||
expect($b(ge._(_n(7))._(_n(3)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinity', () => { |
|
||||||
expect($b(ge._(_n(7))._(infinity))).toBe(false); |
|
||||||
expect($b(ge._(infinity)._(_n(7)))).toBe(true); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('gt', () => { |
|
||||||
it('0 gt 0 is False', () => { |
|
||||||
expect($b(gt._(_n(0))._(_n(0)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 gt 1 is False', () => { |
|
||||||
expect($b(gt._(_n(0))._(_n(1)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('1 gt 0 is True', () => { |
|
||||||
expect($b(gt._(_n(1))._(_n(0)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('3 gt 3 is False', () => { |
|
||||||
expect($b(gt._(_n(3))._(_n(3)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('3 gt 7 is False', () => { |
|
||||||
expect($b(gt._(_n(3))._(_n(7)))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('7 gt 3 is True', () => { |
|
||||||
expect($b(gt._(_n(7))._(_n(3)))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinity', () => { |
|
||||||
expect($b(gt._(_n(7))._(infinity))).toBe(false); |
|
||||||
expect($b(gt._(infinity)._(_n(7)))).toBe(true); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('sum', () => { |
|
||||||
it('0 sum 0 is 0', () => { |
|
||||||
expect($n(sum._(_n(0))._(_n(0)))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 sum 1 is 1', () => { |
|
||||||
expect($n(sum._(_n(0))._(_n(1)))).toBe(1); |
|
||||||
}); |
|
||||||
|
|
||||||
it('1 sum 0 is 1', () => { |
|
||||||
expect($n(sum._(_n(1))._(_n(0)))).toBe(1); |
|
||||||
}); |
|
||||||
|
|
||||||
it('3 sum 4 is 7', () => { |
|
||||||
expect($n(sum._(_n(3))._(_n(4)))).toBe(7); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('sub', () => { |
|
||||||
it('0 sub 0 is 0', () => { |
|
||||||
expect($n(sub._(_n(0))._(_n(0)))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 sub 1 is 0', () => { |
|
||||||
expect($n(sub._(_n(0))._(_n(1)))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('1 sub 0 is 1', () => { |
|
||||||
expect($n(sub._(_n(1))._(_n(0)))).toBe(1); |
|
||||||
}); |
|
||||||
|
|
||||||
it('7 sub 4 is 3', () => { |
|
||||||
expect($n(sub._(_n(7))._(_n(4)))).toBe(3); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('mul', () => { |
|
||||||
it('0 mul 0 is 0', () => { |
|
||||||
expect($n(mul._(_n(0))._(_n(0)))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 mul 10 is 0', () => { |
|
||||||
expect($n(mul._(_n(0))._(_n(10)))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('10 mul 0 is 0', () => { |
|
||||||
expect($n(mul._(_n(1))._(_n(0)))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('1 mul 10 is 10', () => { |
|
||||||
expect($n(mul._(_n(1))._(_n(10)))).toBe(10); |
|
||||||
}); |
|
||||||
|
|
||||||
it('10 mul 1 is 10', () => { |
|
||||||
expect($n(mul._(_n(10))._(_n(1)))).toBe(10); |
|
||||||
}); |
|
||||||
|
|
||||||
it('3 mul 4 is 12', () => { |
|
||||||
expect($n(mul._(_n(3))._(_n(4)))).toBe(12); |
|
||||||
}); |
|
||||||
|
|
||||||
it('0 mul ignores second argument', () => { |
|
||||||
expect($n(mul._(_n(0))._(undef))).toBe(0); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('div', () => { |
|
||||||
it('0 div 10 is 0', () => { |
|
||||||
expect($n(div._(_n(0))._(_n(10)))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('1 div 10 is 0', () => { |
|
||||||
expect($n(div._(_n(1))._(_n(10)))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('10 div 1 is 10', () => { |
|
||||||
expect($n(div._(_n(10))._(_n(1)))).toBe(10); |
|
||||||
}); |
|
||||||
|
|
||||||
it('10 div 5 is 2', () => { |
|
||||||
expect($n(div._(_n(10))._(_n(5)))).toBe(2); |
|
||||||
}); |
|
||||||
|
|
||||||
it('10 div 3 is 3', () => { |
|
||||||
expect($n(div._(_n(10))._(_n(3)))).toBe(3); |
|
||||||
}); |
|
||||||
|
|
||||||
it.failing('0 div ignores second argument', () => { |
|
||||||
expect($n(div._(_n(0))._(undef))).toBe(0); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('mod', () => { |
|
||||||
it('0 mod 10 is 0', () => { |
|
||||||
expect($n(mod._(_n(0))._(_n(10)))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('1 mod 10 is 1', () => { |
|
||||||
expect($n(mod._(_n(1))._(_n(10)))).toBe(1); |
|
||||||
}); |
|
||||||
|
|
||||||
it('10 mod 1 is 0', () => { |
|
||||||
expect($n(mod._(_n(10))._(_n(1)))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('10 mod 5 is 0', () => { |
|
||||||
expect($n(mod._(_n(10))._(_n(5)))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('10 mod 3 is 1', () => { |
|
||||||
expect($n(mod._(_n(10))._(_n(3)))).toBe(1); |
|
||||||
}); |
|
||||||
|
|
||||||
it.failing('0 mod ignores second argument', () => { |
|
||||||
expect($n(mod._(_n(0))._(undef))).toBe(0); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('show', () => { |
|
||||||
it('show 0 is "0"', () => { |
|
||||||
expect($s(show._(_n(0)))).toBe('0'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('show 1 is "1"', () => { |
|
||||||
expect($s(show._(_n(1)))).toBe('1'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('show 255 is "255"', () => { |
|
||||||
expect($s(show._(_n(255)))).toBe('255'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('show 1000 is "1000"', () => { |
|
||||||
expect($s(show._(_n(1000)))).toBe('1000'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
||||||
@ -1,233 +0,0 @@ |
|||||||
import { describe, expect, it } from '@jest/globals'; |
|
||||||
import { undef } from '../src/core'; |
|
||||||
import { toNOrd as $o, NOrd } from '../src/ord'; |
|
||||||
import { toBoolean as $b, fromBoolean as _b, cmp as bcmp, eq as beq, not } from '../src/bool'; |
|
||||||
import { toNumber as $n, fromNumber as _n, cmp as ncmp, eq as neq, succ } from '../src/num'; |
|
||||||
import { Pair, both, cmp, eq, first, fst, ge, gt, le, lt, second, snd } from '../src/pair'; |
|
||||||
import { toString } from '../src/string'; |
|
||||||
import { show as bshow } from '../src/bool.show'; |
|
||||||
import { show as nshow } from '../src/num.show'; |
|
||||||
import { show } from '../src/pair.show'; |
|
||||||
|
|
||||||
describe('Pair', () => { |
|
||||||
describe('fst', () => { |
|
||||||
it('returns first', () => { |
|
||||||
expect($b(fst._(Pair._(_b(false))._(_n(0))))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('ignores second', () => { |
|
||||||
expect($b(fst._(Pair._(_b(false))._(undef)))).toBe(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('snd', () => { |
|
||||||
it('returns second', () => { |
|
||||||
expect($n(snd._(Pair._(_b(false))._(_n(0))))).toBe(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('ignores first', () => { |
|
||||||
expect($n(snd._(Pair._(undef)._(_n(0))))).toBe(0); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('first', () => { |
|
||||||
it('updates first', () => { |
|
||||||
expect($b(fst._(first._(not)._(Pair._(_b(false))._(_n(0)))))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('ignores second', () => { |
|
||||||
expect($b(fst._(first._(not)._(Pair._(_b(false))._(undef))))).toBe(true); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('second', () => { |
|
||||||
it('updates second', () => { |
|
||||||
expect($n(snd._(second._(succ)._(Pair._(_b(false))._(_n(0)))))).toBe(1); |
|
||||||
}); |
|
||||||
|
|
||||||
it('ignores first', () => { |
|
||||||
expect($n(snd._(second._(succ)._(Pair._(undef)._(_n(0)))))).toBe(1); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('both', () => { |
|
||||||
it('updates both', () => { |
|
||||||
expect($b(fst._(both._(not)._(succ)._(Pair._(_b(false))._(_n(0)))))).toBe(true); |
|
||||||
expect($n(snd._(both._(not)._(succ)._(Pair._(_b(false))._(_n(0)))))).toBe(1); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('cmp', () => { |
|
||||||
it('(False, 0) cmp (False, 0) is EQ', () => { |
|
||||||
expect($o(cmp._(bcmp)._(ncmp)._(Pair._(_b(false))._(_n(0)))._(Pair._(_b(false))._(_n(0))))).toBe(NOrd.EQ); |
|
||||||
}); |
|
||||||
|
|
||||||
it('(False, 0) cmp (False, 1) is LT', () => { |
|
||||||
expect($o(cmp._(bcmp)._(ncmp)._(Pair._(_b(false))._(_n(0)))._(Pair._(_b(false))._(_n(1))))).toBe(NOrd.LT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('(False, 1) cmp (False, 0) is GT', () => { |
|
||||||
expect($o(cmp._(bcmp)._(ncmp)._(Pair._(_b(false))._(_n(1)))._(Pair._(_b(false))._(_n(0))))).toBe(NOrd.GT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('(False, 0) cmp (True, 0) is LT', () => { |
|
||||||
expect($o(cmp._(bcmp)._(ncmp)._(Pair._(_b(false))._(_n(0)))._(Pair._(_b(true))._(_n(0))))).toBe(NOrd.LT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('(True, 0) cmp (False, 0) is GT', () => { |
|
||||||
expect($o(cmp._(bcmp)._(ncmp)._(Pair._(_b(true))._(_n(0)))._(Pair._(_b(false))._(_n(0))))).toBe(NOrd.GT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('ignores second if first not equal', () => { |
|
||||||
expect($o(cmp._(bcmp)._(undef)._(Pair._(_b(false))._(undef))._(Pair._(_b(true))._(undef)))).toBe(NOrd.LT); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('lt', () => { |
|
||||||
it('(False, 0) lt (False, 0) is False', () => { |
|
||||||
expect($b(lt._(bcmp)._(ncmp)._(Pair._(_b(false))._(_n(0)))._(Pair._(_b(false))._(_n(0))))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('(False, 0) lt (False, 1) is True', () => { |
|
||||||
expect($b(lt._(bcmp)._(ncmp)._(Pair._(_b(false))._(_n(0)))._(Pair._(_b(false))._(_n(1))))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('(False, 1) lt (False, 0) is False', () => { |
|
||||||
expect($b(lt._(bcmp)._(ncmp)._(Pair._(_b(false))._(_n(1)))._(Pair._(_b(false))._(_n(0))))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('(False, 0) lt (True, 0) is True', () => { |
|
||||||
expect($b(lt._(bcmp)._(ncmp)._(Pair._(_b(false))._(_n(0)))._(Pair._(_b(true))._(_n(0))))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('(True, 0) lt (False, 0) is False', () => { |
|
||||||
expect($b(lt._(bcmp)._(ncmp)._(Pair._(_b(true))._(_n(0)))._(Pair._(_b(false))._(_n(0))))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('ignores second if first not equal', () => { |
|
||||||
expect($b(lt._(bcmp)._(undef)._(Pair._(_b(false))._(undef))._(Pair._(_b(true))._(undef)))).toBe(true); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('le', () => { |
|
||||||
it('(False, 0) le (False, 0) is True', () => { |
|
||||||
expect($b(le._(bcmp)._(ncmp)._(Pair._(_b(false))._(_n(0)))._(Pair._(_b(false))._(_n(0))))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('(False, 0) le (False, 1) is True', () => { |
|
||||||
expect($b(le._(bcmp)._(ncmp)._(Pair._(_b(false))._(_n(0)))._(Pair._(_b(false))._(_n(1))))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('(False, 1) le (False, 0) is False', () => { |
|
||||||
expect($b(le._(bcmp)._(ncmp)._(Pair._(_b(false))._(_n(1)))._(Pair._(_b(false))._(_n(0))))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('(False, 0) le (True, 0) is True', () => { |
|
||||||
expect($b(le._(bcmp)._(ncmp)._(Pair._(_b(false))._(_n(0)))._(Pair._(_b(true))._(_n(0))))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('(True, 0) le (False, 0) is False', () => { |
|
||||||
expect($b(le._(bcmp)._(ncmp)._(Pair._(_b(true))._(_n(0)))._(Pair._(_b(false))._(_n(0))))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('ignores second if first not equal', () => { |
|
||||||
expect($b(le._(bcmp)._(undef)._(Pair._(_b(false))._(undef))._(Pair._(_b(true))._(undef)))).toBe(true); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('eq', () => { |
|
||||||
it('(False, 0) eq (False, 0) is True', () => { |
|
||||||
expect($b(eq._(beq)._(neq)._(Pair._(_b(false))._(_n(0)))._(Pair._(_b(false))._(_n(0))))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('(False, 0) eq (False, 1) is False', () => { |
|
||||||
expect($b(eq._(beq)._(neq)._(Pair._(_b(false))._(_n(0)))._(Pair._(_b(false))._(_n(1))))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('(False, 1) eq (False, 0) is False', () => { |
|
||||||
expect($b(eq._(beq)._(neq)._(Pair._(_b(false))._(_n(1)))._(Pair._(_b(false))._(_n(0))))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('(False, 0) eq (True, 0) is True', () => { |
|
||||||
expect($b(eq._(beq)._(neq)._(Pair._(_b(false))._(_n(0)))._(Pair._(_b(true))._(_n(0))))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('(True, 0) eq (False, 0) is True', () => { |
|
||||||
expect($b(eq._(beq)._(neq)._(Pair._(_b(true))._(_n(0)))._(Pair._(_b(false))._(_n(0))))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('ignores second if first not equal', () => { |
|
||||||
expect($b(eq._(beq)._(undef)._(Pair._(_b(false))._(undef))._(Pair._(_b(true))._(undef)))).toBe(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('ge', () => { |
|
||||||
it('(False, 0) ge (False, 0) is True', () => { |
|
||||||
expect($b(ge._(bcmp)._(ncmp)._(Pair._(_b(false))._(_n(0)))._(Pair._(_b(false))._(_n(0))))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('(False, 0) ge (False, 1) is False', () => { |
|
||||||
expect($b(ge._(bcmp)._(ncmp)._(Pair._(_b(false))._(_n(0)))._(Pair._(_b(false))._(_n(1))))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('(False, 1) ge (False, 0) is True', () => { |
|
||||||
expect($b(ge._(bcmp)._(ncmp)._(Pair._(_b(false))._(_n(1)))._(Pair._(_b(false))._(_n(0))))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('(False, 0) ge (True, 0) is False', () => { |
|
||||||
expect($b(ge._(bcmp)._(ncmp)._(Pair._(_b(false))._(_n(0)))._(Pair._(_b(true))._(_n(0))))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('(True, 0) ge (False, 0) is True', () => { |
|
||||||
expect($b(ge._(bcmp)._(ncmp)._(Pair._(_b(true))._(_n(0)))._(Pair._(_b(false))._(_n(0))))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('ignores second if first not equal', () => { |
|
||||||
expect($b(ge._(bcmp)._(undef)._(Pair._(_b(false))._(undef))._(Pair._(_b(true))._(undef)))).toBe(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('gt', () => { |
|
||||||
it('(False, 0) gt (False, 0) is False', () => { |
|
||||||
expect($b(gt._(bcmp)._(ncmp)._(Pair._(_b(false))._(_n(0)))._(Pair._(_b(false))._(_n(0))))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('(False, 0) gt (False, 1) is False', () => { |
|
||||||
expect($b(gt._(bcmp)._(ncmp)._(Pair._(_b(false))._(_n(0)))._(Pair._(_b(false))._(_n(1))))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('(False, 1) gt (False, 0) is True', () => { |
|
||||||
expect($b(gt._(bcmp)._(ncmp)._(Pair._(_b(false))._(_n(1)))._(Pair._(_b(false))._(_n(0))))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('(False, 0) gt (True, 0) is False', () => { |
|
||||||
expect($b(gt._(bcmp)._(ncmp)._(Pair._(_b(false))._(_n(0)))._(Pair._(_b(true))._(_n(0))))).toBe(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('(True, 0) gt (False, 0) is True', () => { |
|
||||||
expect($b(gt._(bcmp)._(ncmp)._(Pair._(_b(true))._(_n(0)))._(Pair._(_b(false))._(_n(0))))).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('ignores second if first not equal', () => { |
|
||||||
expect($b(gt._(bcmp)._(undef)._(Pair._(_b(false))._(undef))._(Pair._(_b(true))._(undef)))).toBe(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('show', () => { |
|
||||||
it('show (False, 0) is "(False, 0)"', () => { |
|
||||||
expect(toString(show._(bshow)._(nshow)._(Pair._(_b(false))._(_n(0))))).toBe('(False, 0)'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('show (False, 1) is "(False, 1)"', () => { |
|
||||||
expect(toString(show._(bshow)._(nshow)._(Pair._(_b(false))._(_n(1))))).toBe('(False, 1)'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('show (True, 0) is "(True, 0)"', () => { |
|
||||||
expect(toString(show._(bshow)._(nshow)._(Pair._(_b(true))._(_n(0))))).toBe('(True, 0)'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('show (True, 1) is "(True, 1)"', () => { |
|
||||||
expect(toString(show._(bshow)._(nshow)._(Pair._(_b(true))._(_n(1))))).toBe('(True, 1)'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
||||||
@ -1,376 +0,0 @@ |
|||||||
import { describe, expect, it } from '@jest/globals'; |
|
||||||
import { Term, _, _l, g, n, undef } from '../src/core'; |
|
||||||
import { NOrd, toNOrd } from '../src/ord'; |
|
||||||
import { toBoolean as $b } from '../src/bool'; |
|
||||||
import { toNumber as $n, Zero, gt as ngt } from '../src/num'; |
|
||||||
import { toChar as $c, fromChar as _c } from '../src/char'; |
|
||||||
import { toString as $s, Cons, Empty, fromString as _s, append, cmp, empty, eq, ge, gt, le, len, lt, wrap } from '../src/string'; |
|
||||||
import { show } from '../src/string.show'; |
|
||||||
|
|
||||||
n('string.spec'); |
|
||||||
|
|
||||||
describe('String', () => { |
|
||||||
const infinite: Term = g('infinite', _(() => Cons._(_c('a'))._(infinite))); |
|
||||||
|
|
||||||
describe('toString', () => { |
|
||||||
it('converts ""', () => { |
|
||||||
expect($s(Empty)).toBe(''); |
|
||||||
}); |
|
||||||
|
|
||||||
it('converts "a"', () => { |
|
||||||
expect($s(Cons._(_c('a'))._(Empty))).toBe('a'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('converts "abc"', () => { |
|
||||||
expect($s(Cons._(_c('a'))._(Cons._(_c('b'))._(Cons._(_c('c'))._(Empty))))).toBe('abc'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('fromString', () => { |
|
||||||
it('converts ""', () => { |
|
||||||
expect($s(_s(''))).toBe(''); |
|
||||||
}); |
|
||||||
|
|
||||||
it('converts "a"', () => { |
|
||||||
expect($s(_s('a'))).toBe('a'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('converts "2"', () => { |
|
||||||
expect($s(_s('abc'))).toBe('abc'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
// describe('repeat', () => {
|
|
||||||
// it('repeat "a" is "aaa..."', () => {
|
|
||||||
// expect($na(
|
|
||||||
// repeat(_s('a'))<String>(_((x0: Char) => _((x1s: String) =>
|
|
||||||
// x1s<String>(_((x1: Char) => _((x2s: String) =>
|
|
||||||
// x2s<String>(_((x2: Char) => _((_xrs: String): String => cons(x0)._(cons(x1)._(cons(x2)._(Nil)))))))))))))).toEqual('aaa');
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
// describe('iterate', () => {
|
|
||||||
// it('iterate succ "a" is "abc..."', () => {
|
|
||||||
// expect($na(
|
|
||||||
// iterate(_s('a'))<String>(_((x0: Char) => _((x1s: String) =>
|
|
||||||
// x1s<String>(_((x1: Char) => _((x2s: String) =>
|
|
||||||
// x2s<String>(_((x2: Char) => _((_xrs: String): String => cons(x0)._(cons(x1)._(cons(x2)._(Nil)))))))))))))).toEqual('abc');
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
describe('empty', () => { |
|
||||||
it('empty "" is True', () => { |
|
||||||
expect($b(empty._(_s('')))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('empty "a" is False', () => { |
|
||||||
expect($b(empty._(_s('a')))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('empty "ab" is False', () => { |
|
||||||
expect($b(empty._(_s('ab')))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite lists', () => { |
|
||||||
expect($b(empty._(infinite))).toEqual(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('len', () => { |
|
||||||
it('len "" is 0', () => { |
|
||||||
expect($n(len._(_s('')))).toEqual(0); |
|
||||||
}); |
|
||||||
|
|
||||||
it('len "a" is 1', () => { |
|
||||||
expect($n(len._(_s('a')))).toEqual(1); |
|
||||||
}); |
|
||||||
|
|
||||||
it('len "ab" is 2', () => { |
|
||||||
expect($n(len._(_s('ab')))).toEqual(2); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite lists', () => { |
|
||||||
expect($b(ngt._(len._(infinite))._(Zero))).toEqual(true); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('append', () => { |
|
||||||
it('"" append "" is ""', () => { |
|
||||||
expect($s(append._(Empty)._(Empty))).toBe(''); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"a" append "b" is "ab"', () => { |
|
||||||
expect($s(append._(_s('a'))._(_s('b')))).toBe('ab'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"abc" append "def" is "abcdef"', () => { |
|
||||||
expect($s(append._(_s('abc'))._(_s('def')))).toBe('abcdef'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite strings', () => { |
|
||||||
expect($c(append._(infinite)._(infinite)._(undef)._(_l(x => _l(_ => x))))).toBe('a'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('wrap', () => { |
|
||||||
it('"" wrap "" is ""', () => { |
|
||||||
expect($s(wrap._(Empty)._(Empty))).toBe(''); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"a" wrap "b" is "aba"', () => { |
|
||||||
expect($s(wrap._(_s('a'))._(_s('b')))).toBe('aba'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite strings', () => { |
|
||||||
expect($c(wrap._(infinite)._(infinite)._(undef)._(_l(x => _l(_ => x))))).toBe('a'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('cmp', () => { |
|
||||||
it('"" cmp "" is EQ', () => { |
|
||||||
expect(toNOrd(cmp._(_s(''))._(_s('')))).toEqual(NOrd.EQ); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"" cmp "abc" is LT', () => { |
|
||||||
expect(toNOrd(cmp._(_s(''))._(_s('abc')))).toEqual(NOrd.LT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"abc" cmp "" is GT', () => { |
|
||||||
expect(toNOrd(cmp._(_s('abc'))._(_s('')))).toEqual(NOrd.GT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"a" cmp "abc" is LT', () => { |
|
||||||
expect(toNOrd(cmp._(_s('a'))._(_s('abc')))).toEqual(NOrd.LT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"abc" cmp "a" is GT', () => { |
|
||||||
expect(toNOrd(cmp._(_s('abc'))._(_s('a')))).toEqual(NOrd.GT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"bca" cmp "abc" is GT', () => { |
|
||||||
expect(toNOrd(cmp._(_s('bca'))._(_s('abc')))).toEqual(NOrd.GT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"abc" cmp "bca" is LT', () => { |
|
||||||
expect(toNOrd(cmp._(_s('abc'))._(_s('bca')))).toEqual(NOrd.LT); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"abc" cmp "abc" is EQ', () => { |
|
||||||
expect(toNOrd(cmp._(_s('abc'))._(_s('abc')))).toEqual(NOrd.EQ); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite strings', () => { |
|
||||||
expect(toNOrd(cmp._(_s('abc'))._(infinite))).toEqual(NOrd.GT); |
|
||||||
expect(toNOrd(cmp._(infinite)._(_s('abc')))).toEqual(NOrd.LT); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('lt', () => { |
|
||||||
it('"" lt "" is False', () => { |
|
||||||
expect($b(lt._(_s(''))._(_s('')))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"" lt "abc" is True', () => { |
|
||||||
expect($b(lt._(_s(''))._(_s('abc')))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"abc" lt "" is False', () => { |
|
||||||
expect($b(lt._(_s('abc'))._(_s('')))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"a" lt "abc" is True', () => { |
|
||||||
expect($b(lt._(_s('a'))._(_s('abc')))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"abc" lt "a" is False', () => { |
|
||||||
expect($b(lt._(_s('abc'))._(_s('a')))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"bca" lt "abc" is False', () => { |
|
||||||
expect($b(lt._(_s('bca'))._(_s('abc')))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"abc" lt "bca" is True', () => { |
|
||||||
expect($b(lt._(_s('abc'))._(_s('bca')))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"abc" lt "abc" is False', () => { |
|
||||||
expect($b(lt._(_s('abc'))._(_s('abc')))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite strings', () => { |
|
||||||
expect($b(lt._(_s('abc'))._(infinite))).toEqual(false); |
|
||||||
expect($b(lt._(infinite)._(_s('abc')))).toEqual(true); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('le', () => { |
|
||||||
it('"" le "" is True', () => { |
|
||||||
expect($b(le._(_s(''))._(_s('')))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"" le "abc" is True', () => { |
|
||||||
expect($b(le._(_s(''))._(_s('abc')))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"abc" le "" is False', () => { |
|
||||||
expect($b(le._(_s('abc'))._(_s('')))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"a" le "abc" is True', () => { |
|
||||||
expect($b(le._(_s('a'))._(_s('abc')))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"abc" le "a" is False', () => { |
|
||||||
expect($b(le._(_s('abc'))._(_s('a')))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"bca" le "abc" is False', () => { |
|
||||||
expect($b(le._(_s('bca'))._(_s('abc')))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"abc" le "bca" is True', () => { |
|
||||||
expect($b(le._(_s('abc'))._(_s('bca')))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"abc" le "abc" is True', () => { |
|
||||||
expect($b(le._(_s('abc'))._(_s('abc')))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite strings', () => { |
|
||||||
expect($b(le._(_s('abc'))._(infinite))).toEqual(false); |
|
||||||
expect($b(le._(infinite)._(_s('abc')))).toEqual(true); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('eq', () => { |
|
||||||
it('"" eq "" is True', () => { |
|
||||||
expect($b(eq._(_s(''))._(_s('')))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"" eq "abc" is False', () => { |
|
||||||
expect($b(eq._(_s(''))._(_s('abc')))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"abc" eq "" is False', () => { |
|
||||||
expect($b(eq._(_s('abc'))._(_s('')))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"a" eq "abc" is False', () => { |
|
||||||
expect($b(eq._(_s('a'))._(_s('abc')))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"abc" eq "a" is False', () => { |
|
||||||
expect($b(eq._(_s('abc'))._(_s('a')))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"bca" eq "abc" is False', () => { |
|
||||||
expect($b(eq._(_s('bca'))._(_s('abc')))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"abc" eq "bca" is False', () => { |
|
||||||
expect($b(eq._(_s('abc'))._(_s('bca')))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"abc" eq "abc" is True', () => { |
|
||||||
expect($b(eq._(_s('abc'))._(_s('abc')))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite strings', () => { |
|
||||||
expect($b(eq._(_s('abc'))._(infinite))).toEqual(false); |
|
||||||
expect($b(eq._(infinite)._(_s('abc')))).toEqual(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('ge', () => { |
|
||||||
it('"" ge "" is True', () => { |
|
||||||
expect($b(ge._(_s(''))._(_s('')))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"" ge "abc" is False', () => { |
|
||||||
expect($b(ge._(_s(''))._(_s('abc')))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"abc" ge "" is True', () => { |
|
||||||
expect($b(ge._(_s('abc'))._(_s('')))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"a" ge "abc" is False', () => { |
|
||||||
expect($b(ge._(_s('a'))._(_s('abc')))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"abc" ge "a" is True', () => { |
|
||||||
expect($b(ge._(_s('abc'))._(_s('a')))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"bca" ge "abc" is True', () => { |
|
||||||
expect($b(ge._(_s('bca'))._(_s('abc')))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"abc" ge "bca" is False', () => { |
|
||||||
expect($b(ge._(_s('abc'))._(_s('bca')))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"abc" ge "abc" is True', () => { |
|
||||||
expect($b(ge._(_s('abc'))._(_s('abc')))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite strings', () => { |
|
||||||
expect($b(ge._(_s('abc'))._(infinite))).toEqual(true); |
|
||||||
expect($b(ge._(infinite)._(_s('abc')))).toEqual(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('gt', () => { |
|
||||||
it('"" gt "" is False', () => { |
|
||||||
expect($b(gt._(_s(''))._(_s('')))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"" gt "abc" is False', () => { |
|
||||||
expect($b(gt._(_s(''))._(_s('abc')))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"abc" gt "" is True', () => { |
|
||||||
expect($b(gt._(_s('abc'))._(_s('')))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"a" gt "abc" is False', () => { |
|
||||||
expect($b(gt._(_s('a'))._(_s('abc')))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"abc" gt "a" is True', () => { |
|
||||||
expect($b(gt._(_s('abc'))._(_s('a')))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"bca" gt "abc" is True', () => { |
|
||||||
expect($b(gt._(_s('bca'))._(_s('abc')))).toEqual(true); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"abc" gt "bca" is False', () => { |
|
||||||
expect($b(gt._(_s('abc'))._(_s('bca')))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('"abc" gt "abc" is False', () => { |
|
||||||
expect($b(gt._(_s('abc'))._(_s('abc')))).toEqual(false); |
|
||||||
}); |
|
||||||
|
|
||||||
it('handles infinite strings', () => { |
|
||||||
expect($b(gt._(_s('abc'))._(infinite))).toEqual(true); |
|
||||||
expect($b(gt._(infinite)._(_s('abc')))).toEqual(false); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('show', () => { |
|
||||||
it('show "" is """"', () => { |
|
||||||
expect($s(show._(_s('')))).toBe('""'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('show "a" is ""a""', () => { |
|
||||||
expect($s(show._(_s('a')))).toBe('"a"'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('show "abc" is ""abc""', () => { |
|
||||||
expect($s(show._(_s('abc')))).toBe('"abc"'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
||||||
@ -0,0 +1,35 @@ |
|||||||
|
#!/bin/bash |
||||||
|
|
||||||
|
model=$1 |
||||||
|
count=${2:-9000} |
||||||
|
|
||||||
|
mkdir -p ${model}/downloads |
||||||
|
rm -rf ${model}/downloads/* |
||||||
|
|
||||||
|
BOOK_IDS=( |
||||||
|
174 1342 11 2701 84 2600 1661 215 1952 98 345 35 1497 2147 4300 |
||||||
|
) |
||||||
|
|
||||||
|
echo "Downloading books..." |
||||||
|
|
||||||
|
i=1 |
||||||
|
for ID in "${BOOK_IDS[@]}"; do |
||||||
|
echo "Fetching $ID..." |
||||||
|
curl -s -L "https://www.gutenberg.org/files/$ID/$ID-0.txt" -o "${model}/downloads/$ID.txt" |
||||||
|
i=$((i+1)) |
||||||
|
if [ ${i} -gt ${count} ]; then |
||||||
|
break; |
||||||
|
fi |
||||||
|
done |
||||||
|
|
||||||
|
echo "Concatenating into corpus.txt..." |
||||||
|
|
||||||
|
# Remove headers/footers and join them |
||||||
|
rm -f ${model}/corpus.txt |
||||||
|
for FILE in ${model}/downloads/*.txt; do |
||||||
|
awk '/\*\*\* START OF/,/\*\*\* END OF/ { if (!/START|END/) print }' "$FILE" >> ${model}/corpus.txt |
||||||
|
echo >> ${model}/corpus.txt |
||||||
|
done |
||||||
|
|
||||||
|
echo "All done. Corpus size:" |
||||||
|
du -h ${model}/corpus.txt |
||||||
@ -0,0 +1,178 @@ |
|||||||
|
import sys |
||||||
|
import pandas as pd |
||||||
|
import matplotlib.pyplot as plt |
||||||
|
import matplotlib.animation as animation |
||||||
|
import matplotlib.dates as mdates # Add this import at the top |
||||||
|
import select |
||||||
|
from datetime import datetime |
||||||
|
import threading |
||||||
|
import queue |
||||||
|
|
||||||
|
fig = plt.figure(figsize=(10, 6)) |
||||||
|
|
||||||
|
ax_lr = fig.add_axes([0.05, 0.3, 0.9, 0.65]) |
||||||
|
ax_lr.set_facecolor("none") |
||||||
|
ax_lr.set_ylim(bottom=0, top=None) |
||||||
|
ax_lr.yaxis.set_ticks_position('right') |
||||||
|
ax_lr.tick_params(axis='y', labelcolor='tab:brown') |
||||||
|
|
||||||
|
ax_acc = fig.add_axes([0.05, 0.3, 0.9, 0.65], sharex=ax_lr) |
||||||
|
ax_acc.set_facecolor("none") |
||||||
|
ax_acc.set_ylim(0, 100) |
||||||
|
ax_acc.yaxis.set_ticks_position('right') |
||||||
|
ax_acc.tick_params(axis='y', labelcolor='tab:green') |
||||||
|
|
||||||
|
ax_loss = fig.add_axes([0.05, 0.3, 0.9, 0.65], sharex=ax_lr) |
||||||
|
ax_loss.set_facecolor("none") |
||||||
|
ax_loss.set_ylim(bottom=0, top=None) |
||||||
|
ax_loss.tick_params(axis='y', labelcolor='tab:blue') |
||||||
|
|
||||||
|
ax_time = fig.add_axes([0.05, 0.05, 0.9, 0.2]) |
||||||
|
ax_time.set_facecolor("none") |
||||||
|
ax_time.set_ylim(bottom=0, top=None) |
||||||
|
ax_time.yaxis.set_ticks_position('right') |
||||||
|
ax_time.tick_params(axis='y', labelcolor='tab:orange') |
||||||
|
|
||||||
|
columns = ["index", "timestamp", "epoch", "batch", "lr", "loss", "accuracy", "grad_inp", "grad_qry", "grad_key", "grad_val", "grad_out", "queue_ms", "compute_ms", "infer_ms"] |
||||||
|
df = pd.DataFrame(columns=columns) |
||||||
|
ema_alpha = 0.1 |
||||||
|
window_size = 50 |
||||||
|
|
||||||
|
def parse_line(line): |
||||||
|
try: |
||||||
|
parts = line.strip().split(",") |
||||||
|
if parts[0] == "index" or len(parts) < len(columns): |
||||||
|
return None |
||||||
|
row = { |
||||||
|
"index": int(parts[0]), |
||||||
|
"timestamp": datetime.fromtimestamp(int(parts[1]) / 1000), |
||||||
|
"epoch": int(parts[2]), |
||||||
|
"batch": int(parts[3]), |
||||||
|
"lr": float(parts[4]), |
||||||
|
"loss": float(parts[5]), |
||||||
|
"accuracy": float(parts[6]) * 100, |
||||||
|
"grad_inp": float(parts[7]), |
||||||
|
"grad_qry": float(parts[8]), |
||||||
|
"grad_key": float(parts[9]), |
||||||
|
"grad_val": float(parts[10]), |
||||||
|
"grad_out": float(parts[11]), |
||||||
|
"queue_ms": float(parts[12]), |
||||||
|
"compute_ms": float(parts[13]), |
||||||
|
"infer_ms": float(parts[14]), |
||||||
|
} |
||||||
|
return row |
||||||
|
except Exception as e: |
||||||
|
print(f"Bad line: {line.strip()} ({e})", file=sys.stderr) |
||||||
|
return None |
||||||
|
|
||||||
|
line_queue = queue.Queue() |
||||||
|
|
||||||
|
def stdin_reader(): |
||||||
|
for line in sys.stdin: |
||||||
|
line_queue.put(line) |
||||||
|
|
||||||
|
threading.Thread(target=stdin_reader, daemon=True).start() |
||||||
|
|
||||||
|
def get_new_lines(): |
||||||
|
lines = [] |
||||||
|
while not line_queue.empty(): |
||||||
|
lines.append(line_queue.get()) |
||||||
|
return lines |
||||||
|
|
||||||
|
last_seen_index = None |
||||||
|
|
||||||
|
def animate(_): |
||||||
|
global df |
||||||
|
|
||||||
|
lines = get_new_lines() |
||||||
|
if not lines: |
||||||
|
return |
||||||
|
|
||||||
|
new_rows = [parse_line(line) for line in lines] |
||||||
|
new_rows = [r for r in new_rows if r is not None] |
||||||
|
|
||||||
|
if not new_rows: |
||||||
|
return |
||||||
|
|
||||||
|
global last_seen_index |
||||||
|
|
||||||
|
new_index = new_rows[-1]["index"] |
||||||
|
if last_seen_index and (new_index < last_seen_index): |
||||||
|
print("Index reset, restarting plot") |
||||||
|
df = pd.DataFrame(columns=columns) |
||||||
|
|
||||||
|
last_seen_index = new_index |
||||||
|
|
||||||
|
df_new = pd.DataFrame(new_rows) |
||||||
|
df_new["total_ms"] = df_new["queue_ms"] + df_new["compute_ms"] + df_new["infer_ms"] |
||||||
|
df = pd.concat([df, df_new], ignore_index=True) |
||||||
|
|
||||||
|
df["ema_loss"] = df["loss"].ewm(alpha=ema_alpha).mean().combine_first(df["loss"]) |
||||||
|
df["avg_loss"] = df["loss"].rolling(window=window_size).mean().combine_first(df["loss"]) |
||||||
|
df["avg_accuracy"] = df["accuracy"].rolling(window=window_size).mean().combine_first(df["accuracy"]) |
||||||
|
df["avg_total_ms"] = df["total_ms"].rolling(window=window_size).mean().combine_first(df["total_ms"]) |
||||||
|
|
||||||
|
ax_lr.clear() |
||||||
|
ax_lr.set_ylabel("LR") |
||||||
|
ax_lr.yaxis.set_label_position("right") |
||||||
|
|
||||||
|
ax_acc.clear() |
||||||
|
ax_acc.set_ylabel("Accuracy (%)") |
||||||
|
|
||||||
|
ax_loss.clear() |
||||||
|
ax_loss.set_title("Live Training Metrics") |
||||||
|
ax_loss.set_xlabel("Epoch / Batch") |
||||||
|
ax_loss.set_ylabel("Loss") |
||||||
|
ax_acc.yaxis.set_label_position("right") |
||||||
|
|
||||||
|
ax_time.clear() |
||||||
|
ax_time.set_xlabel("Time") |
||||||
|
ax_time.set_ylabel("Processing Time (ms)") |
||||||
|
ax_time.yaxis.set_label_position("right") |
||||||
|
|
||||||
|
ax_lr.plot(df["index"], df["lr"], label="LR", linestyle='--', alpha=0.3, color="tab:brown") |
||||||
|
|
||||||
|
ax_acc.plot(df["index"], df["accuracy"], label="Accuracy", linestyle='--', alpha=0.3, color="tab:green") |
||||||
|
ax_acc.plot(df["index"], df["avg_accuracy"], label="Avg Accuracy", color="tab:green") |
||||||
|
|
||||||
|
ax_loss.plot(df["index"], df["loss"], label="Loss", linestyle=':', alpha=0.3, color="tab:blue") |
||||||
|
ax_loss.plot(df["index"], df["ema_loss"], label="EMA Loss", linestyle='--', alpha=0.7, color="tab:blue") |
||||||
|
ax_loss.plot(df["index"], df["avg_loss"], label=f"Avg Loss", color="tab:blue") |
||||||
|
|
||||||
|
ax_time.plot(df["index"], df["queue_ms"], label="Queueing", linestyle=':', alpha=0.3, color="#ffa64d") |
||||||
|
ax_time.plot(df["index"], df["compute_ms"], label="Computing", linestyle=':', alpha=0.3, color="#cc6600") |
||||||
|
ax_time.plot(df["index"], df["infer_ms"], label="Inferring", linestyle=':', alpha=0.3, color="#ffb84d") |
||||||
|
ax_time.plot(df["index"], df["total_ms"], label="Total Time",linestyle='--', alpha=0.7, color="tab:orange") |
||||||
|
ax_time.plot(df["index"], df["avg_total_ms"], label="Avg Total Time", color="tab:orange") |
||||||
|
|
||||||
|
# Add epoch/batch label ticks to secondary X axis |
||||||
|
tick_locs = ax_time.get_xticks() |
||||||
|
tick_labels = [] |
||||||
|
|
||||||
|
if not df.empty: |
||||||
|
for loc in tick_locs: |
||||||
|
idx = (abs(df["index"] - loc)).argmin() |
||||||
|
e, b = int(df.iloc[idx]["epoch"]), int(df.iloc[idx]["batch"]) |
||||||
|
tick_labels.append(f"{e + 1}/{b + 1}") |
||||||
|
else: |
||||||
|
tick_labels = [""] * len(tick_locs) |
||||||
|
|
||||||
|
ax_loss.set_xlim(ax_time.get_xlim()) |
||||||
|
ax_loss.set_xticks(tick_locs) |
||||||
|
ax_loss.set_xticklabels(tick_labels) |
||||||
|
|
||||||
|
# Gather all legend entries |
||||||
|
lines = [] |
||||||
|
labels = [] |
||||||
|
|
||||||
|
for ax in [ax_lr, ax_acc, ax_loss]: |
||||||
|
l, lab = ax.get_legend_handles_labels() |
||||||
|
lines += l |
||||||
|
labels += lab |
||||||
|
|
||||||
|
# Add combined legend to ax_loss |
||||||
|
ax_loss.legend(lines, labels, loc='upper left') # or 'upper right', etc. |
||||||
|
ax_time.legend(loc='upper left') |
||||||
|
|
||||||
|
ani = animation.FuncAnimation(fig, animate, interval=1000) |
||||||
|
plt.show() |
||||||
Loading…
Reference in new issue