src/image-pseudo-embeddings.test.lua

82 lines

1-- Tests for image-pseudo-embeddings.lua (Issue 9-013 redesign).
2-- Run: luajit src/image-pseudo-embeddings.test.lua
3-- Pure math + string checks; no I/O, no GPU. Fast and deterministic.
4package.path = "./src/?.lua;./libs/?.lua;" .. package.path
5local M = require("image-pseudo-embeddings")
6
7local passed, failed = 0, 0
8local function check(name, cond)
9 if cond then passed = passed + 1
10 else failed = failed + 1; print(" FAIL: " .. name) end
11end
12local function approx(a, b) return math.abs(a - b) < 1e-6 end
13local function vec_approx(v, expected)
14 if #v ~= #expected then return false end
15 for i = 1, #v do if not approx(v[i], expected[i]) then return false end end
16 return true
17end
18local function by_id(list)
19 local m = {} for _, p in ipairs(list) do m[p.id] = p end return m
20end
21
22local R2 = 1 / math.sqrt(2) -- 0.7071...
23
24-- Chronological spine: three orthogonal unit embeddings at t=100,200,300.
25local poems = {
26 { poem_index = 1, timestamp = 100, embedding = {1, 0, 0} },
27 { poem_index = 2, timestamp = 200, embedding = {0, 1, 0} },
28 { poem_index = 3, timestamp = 300, embedding = {0, 0, 1} },
29}
30
31local images = {
32 { id = "between12", source_name = "my-art", rel_below_source = "a.png", timestamp = 150 },
33 { id = "before", source_name = "my-art", rel_below_source = "b.png", timestamp = 50 },
34 { id = "after", source_name = "my-art", rel_below_source = "c.png", timestamp = 350 },
35 { id = "exact2", source_name = "my-art", rel_below_source = "d.png", timestamp = 200 },
36 { id = "between23", source_name = "my-art", rel_below_source = "sub/e.png", timestamp = 250 },
37}
38
39local pseudo, skipped = M.compute_image_pseudo_embeddings(poems, images)
40local p = by_id(pseudo)
41
42check("all 5 images got pseudo-embeddings", #pseudo == 5 and #skipped == 0)
43
44-- Crooked cross-cut (seam = floor(3*0.5) = 1): dim 1 from the BEFORE poem,
45-- dims 2-3 from the AFTER poem. Between p1 (1,0,0) and p2 (0,1,0): dim1 from p1
46-- (1), dims2-3 from p2 (1,0) -> (1,1,0) -> normalized (R2,R2,0). (Same value as
47-- the old midpoint here only by coincidence of the orthogonal toy vectors.)
48check("between12 crooked cross-cut", vec_approx(p.between12.embedding, {R2, R2, 0}))
49-- Before the first poem: leans only on p1 -> (1,0,0).
50check("before-first uses following poem", vec_approx(p.before.embedding, {1, 0, 0}))
51-- After the last poem: leans only on p3 -> (0,0,1).
52check("after-last uses preceding poem", vec_approx(p.after.embedding, {0, 0, 1}))
53-- Exact timestamp match snaps to that poem (p2), not an average across it.
54check("exact-match snaps to that poem", vec_approx(p.exact2.embedding, {0, 1, 0}))
55-- Between p2 (0,1,0) and p3 (0,0,1): dim1 from p2 (0), dims2-3 from p3 (0,1) ->
56-- (0,0,1). With only 3 toy dims and seam=1 the cross-cut leans hard to the AFTER
57-- poem; in real 768-dim space (seam 384) it is a genuine subject/texture blend.
58check("between23 crooked cross-cut", vec_approx(p.between23.embedding, {0, 0, 1}))
59
60-- Every pseudo-embedding is unit length.
61for _, e in ipairs(pseudo) do
62 local s = 0; for i = 1, #e.embedding do s = s + e.embedding[i]^2 end
63 check("unit length: " .. e.id, approx(s, 1))
64end
65
66-- Title formatting (shared with 10-042d).
67check("title: top-level",
68 M.qualified_image_title("my-art", "air-defence-drones-5.png") == "my-art: air-defence-drones-5.png")
69check("title: nested",
70 M.qualified_image_title("my-art", "game-design/camera-idea.png") == "my-art: game-design: camera-idea.png")
71check("title: leading slash tolerated",
72 M.qualified_image_title("my-art", "/game-design/camera-idea.png") == "my-art: game-design: camera-idea.png")
73check("title: display_title carried on pseudo-poem",
74 p.between23.display_title == "my-art: sub: e.png")
75
76-- Empty timeline -> every image skipped (no neighbours), reported not crashed.
77local none, all_skipped = M.compute_image_pseudo_embeddings({}, images)
78check("empty timeline skips all images", #none == 0 and #all_skipped == 5)
79
80print(string.format("\nimage-pseudo-embeddings: %d passed, %d failed", passed, failed))
81os.exit(failed == 0 and 0 or 1)
82