rogermt commited on
Commit
b0f731e
·
verified ·
1 Parent(s): 665bd69

Add comprehensive test suite for all transforms (40 tests)

Browse files
Files changed (1) hide show
  1. tests/test_transforms.py +156 -0
tests/test_transforms.py ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Unit tests for all transforms in itt_solver.transforms.
3
+
4
+ Usage:
5
+ python tests/test_transforms.py
6
+
7
+ 40 tests covering: Kronecker, mirror tiles, upscale, downscale, stack,
8
+ rotate, reflect, color ops, gravity, crop, transpose, shifted tile,
9
+ fill enclosed.
10
+ """
11
+ import numpy as np
12
+ from itt_solver import transforms as tr
13
+
14
+ INP = np.array([[0,7,7],[7,7,7],[0,7,7]], dtype=float)
15
+
16
+ tests_passed = 0
17
+ tests_failed = 0
18
+
19
+ def check(name, condition):
20
+ global tests_passed, tests_failed
21
+ if condition:
22
+ print(f" ✅ {name}")
23
+ tests_passed += 1
24
+ else:
25
+ print(f" ❌ {name}")
26
+ tests_failed += 1
27
+
28
+ print("=== Kronecker Self-Similar ===")
29
+ T = tr.KroneckerSelfSimilar()
30
+ out = T.apply(INP)
31
+ check("Output shape is 9x9", out.shape == (9, 9))
32
+ check("σ=0 vs known target", np.array_equal(out, np.kron((INP!=0).astype(float), INP)))
33
+
34
+ print("\n=== KroneckerSelfSimilarInv ===")
35
+ T = tr.KroneckerSelfSimilarInv()
36
+ out = T.apply(INP)
37
+ check("Output shape is 9x9", out.shape == (9, 9))
38
+
39
+ print("\n=== MirrorTileH ===")
40
+ T = tr.MirrorTileH()
41
+ out = T.apply(INP)
42
+ check("Shape is 3x6", out.shape == (3, 6))
43
+ check("Left half is input", np.array_equal(out[:, :3], INP))
44
+ check("Right half is fliplr(input)", np.array_equal(out[:, 3:], np.fliplr(INP)))
45
+
46
+ print("\n=== MirrorTileV ===")
47
+ T = tr.MirrorTileV()
48
+ out = T.apply(INP)
49
+ check("Shape is 6x3", out.shape == (6, 3))
50
+ check("Top half is input", np.array_equal(out[:3, :], INP))
51
+ check("Bottom half is flipud(input)", np.array_equal(out[3:, :], np.flipud(INP)))
52
+
53
+ print("\n=== MirrorTile4Way ===")
54
+ T = tr.MirrorTile4Way()
55
+ out = T.apply(INP)
56
+ check("Shape is 6x6", out.shape == (6, 6))
57
+
58
+ print("\n=== Upscale 2x ===")
59
+ T = tr.Upscale(2)
60
+ out = T.apply(INP)
61
+ check("Shape is 6x6", out.shape == (6, 6))
62
+ check("Top-left 2x2 block is INP[0,0]", np.all(out[:2, :2] == INP[0, 0]))
63
+
64
+ print("\n=== Upscale 3x ===")
65
+ T = tr.Upscale(3)
66
+ out = T.apply(INP)
67
+ check("Shape is 9x9", out.shape == (9, 9))
68
+ check("Top-left 3x3 block is INP[0,0]", np.all(out[:3, :3] == INP[0, 0]))
69
+
70
+ print("\n=== Downscale 2x ===")
71
+ T = tr.Downscale(2)
72
+ big = np.kron(INP, np.ones((2, 2)))
73
+ out = T.apply(big)
74
+ check("Downscale of upscaled recovers original", np.array_equal(out, INP))
75
+
76
+ print("\n=== StackH 3 ===")
77
+ T = tr.StackH(3)
78
+ out = T.apply(INP)
79
+ check("Shape is 3x9", out.shape == (3, 9))
80
+ check("First third is input", np.array_equal(out[:, :3], INP))
81
+
82
+ print("\n=== StackV 3 ===")
83
+ T = tr.StackV(3)
84
+ out = T.apply(INP)
85
+ check("Shape is 9x3", out.shape == (9, 3))
86
+ check("First third is input", np.array_equal(out[:3, :], INP))
87
+
88
+ print("\n=== Rotate 90/180/270 ===")
89
+ for k in [1, 2, 3]:
90
+ T = tr.Rotate(k)
91
+ out = T.apply(INP)
92
+ check(f"Rotate_{90*k} matches np.rot90", np.array_equal(out, np.rot90(INP, k)))
93
+
94
+ print("\n=== Reflect h/v ===")
95
+ T = tr.Reflect('h')
96
+ check("Reflect_h matches flipud", np.array_equal(T.apply(INP), np.flipud(INP)))
97
+ T = tr.Reflect('v')
98
+ check("Reflect_v matches fliplr", np.array_equal(T.apply(INP), np.fliplr(INP)))
99
+
100
+ print("\n=== RetainColor ===")
101
+ T = tr.RetainColor(7)
102
+ out = T.apply(INP)
103
+ check("Only 7s remain", np.all(out[INP == 7] == 7))
104
+ check("Non-7 positions are 0", np.all(out[INP != 7] == 0))
105
+
106
+ print("\n=== RemoveColor ===")
107
+ T = tr.RemoveColor(7)
108
+ out = T.apply(INP)
109
+ check("7s are removed", np.all(out[INP == 7] == 0))
110
+ check("0s stay 0", np.all(out[INP == 0] == 0))
111
+
112
+ print("\n=== InvertColors ===")
113
+ T = tr.InvertColors()
114
+ out = T.apply(INP)
115
+ check("0→7 swap", np.all(out[INP == 0] == 7))
116
+ check("7→0 swap", np.all(out[INP == 7] == 0))
117
+
118
+ print("\n=== GravityDown ===")
119
+ T = tr.GravityDown()
120
+ col_in = np.array([[0,7,0],[0,0,7],[7,0,0]], dtype=float)
121
+ out = T.apply(col_in)
122
+ check("Col 0: 7 at bottom", out[2, 0] == 7 and out[0, 0] == 0 and out[1, 0] == 0)
123
+ check("Col 1: 7 at bottom", out[2, 1] == 7 and out[0, 1] == 0)
124
+
125
+ print("\n=== GravityUp ===")
126
+ T = tr.GravityUp()
127
+ out = T.apply(col_in)
128
+ check("Col 0: 7 at top", out[0, 0] == 7 and out[1, 0] == 0 and out[2, 0] == 0)
129
+
130
+ print("\n=== CropToContent ===")
131
+ T = tr.CropToContent()
132
+ padded = np.array([[0,0,0,0],[0,7,7,0],[0,7,7,0],[0,0,0,0]], dtype=float)
133
+ out = T.apply(padded)
134
+ check("Crops to 2x2", out.shape == (2, 2))
135
+ check("All 7s", np.all(out == 7))
136
+
137
+ print("\n=== Transpose ===")
138
+ T = tr.Transpose()
139
+ out = T.apply(INP)
140
+ check("Shape is transposed", out.shape == (3, 3))
141
+ check("Values match transpose", np.array_equal(out, INP.T))
142
+
143
+ print("\n=== ShiftedTile ===")
144
+ T = tr.tile_to_target_shifted(shift=(1, 1), tile_factor=3)
145
+ out = T.apply(INP)
146
+ check("Shape is 9x9", out.shape == (9, 9))
147
+ check("Differs from vanilla tile", not np.array_equal(out, np.tile(INP, (3, 3))))
148
+
149
+ print("\n=== FillEnclosedHarmonic ===")
150
+ T = tr.FillEnclosedHarmonic()
151
+ enclosed = np.array([[7,7,7],[7,0,7],[7,7,7]], dtype=float)
152
+ out = T.apply(enclosed)
153
+ check("Center hole filled", out[1, 1] == 7)
154
+
155
+ print(f"\n{'='*50}")
156
+ print(f"Results: {tests_passed} passed, {tests_failed} failed")